/* $Cambridge: hermes/src/prayer/session/draft.c,v 1.25 2011/06/22 15:34:36 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

/* Copyright (c) University of Cambridge 2000 - 2008 */
/* See the file NOTICE for conditions of use and distribution. */

#include "prayer_session.h"

/* Class to support draft management */

/* ====================================================================== */

/* Small utility routines stolen from PINE */

static STRINGLIST *new_strlst(char **l)
{
    STRINGLIST *sl = mail_newstringlist();

    sl->text.data = (unsigned char *) (*l);
    sl->text.size = strlen(*l);
    sl->next = (*++l) ? new_strlst(l) : NULL;
    return (sl);
}

static char *
generate_message_id(struct config *config)
{
    static char  id[128];
    static short osec = 0, cnt = 0;
    time_t       now;
    struct tm   *now_x;

    now   = time((time_t *)0);
    now_x = localtime(&now);

    if(now_x->tm_sec == osec)
      cnt++;
    else{
	cnt = 0;
	osec = now_x->tm_sec;
    }

    snprintf(id, 127, "<Prayer.%.20s.%02d%02d%02d%02d%02d%02d%X.%d@%.50s>",
	    VERSION_PRAYER, (now_x->tm_year) % 100, now_x->tm_mon + 1,
	    now_x->tm_mday, now_x->tm_hour, now_x->tm_min, now_x->tm_sec, 
	    cnt, getpid(), config->hostname_canonical);

    return(id);
}

/* ====================================================================== */

/* Pair of small utility rountines */

static char *get_single_hdr(char **sp)
{
    char *s = *sp;
    char *t;
    char *result = s;

    if (!(s && s[0]))
        return(NULL);

    while (1) {
        while (*s && (*s != '\015') && (*s != '\012'))
            s++;

        if (s[0] == '\0')
            break;

        t = s;
        s += ((s[0] == '\015') && (s[1] == '\012')) ? 2 : 1;

        if ((*s != ' ') && (*s != '\t')) {
            *t = '\0';   /* Reached CRLF with no continuation line */
            break;
        }
    }
    *sp = s;
    return(result);
}

static char *
format_references(struct pool *pool, char *s)
{
    struct buffer *b = buffer_create(pool, 128);
    int line = 0;

    while (*s) {
        while (*s && Uisspace(*s))
            s++;

        if (*s && ((++line) > 1))
            bputs(b, CRLF" ");

        while (*s && !Uisspace(*s)) {
            bputc(b, *s);
            s++;
        }
    }

    return(buffer_fetch(b, 0, buffer_size(b), NIL));
}

/* Convert UTF-8 string to RFC1522 encoding (but only if required).  Label
 * as ISO-8851 if only Latin 1 characters actually in use.  Uses temporary
 * scratch buffer (same size as PINE). Final result always pool_strdup()ed
 * from the pool that we are given. 
 */
static char *
draft_rfc1522_encode(struct pool *pool, char *s, BOOL *encodedp)
{
    char tmp[20*1024];  /* PINE uses scratch buffer this size */
    BOOL is_8859_1 = utf8_is_8859_1(s);
    BOOL encoded = NIL;

    if (is_8859_1) {
        s = pool_strdup(pool, s);
        utf8_to_8859_1(s);
    }
    s = (char *) rfc1522_encode(tmp, sizeof(tmp),
                                (unsigned char *)s,
                                (is_8859_1) ? "ISO-8859-1" : "UTF-8");
    if (s == tmp) {
        encoded = T;
        s = pool_strdup(pool, s);
    }
    if (encodedp)
        *encodedp = encoded;
    
    return(s);
}

/* ====================================================================== */

/* draft_print_subject() is modified version of draft_rfc1522_encode()
 * which wraps subject line when rfc1522 encoder is NOOP.
 */

static int
mywordlen(char *s)
{
    int len = 0;

    while (*s && !string_isspace(*s)) {
        s++;
        len++;
    }

    return(len);
}

static void
draft_print_subject_fold(struct buffer *b, char *s)
{
    int col = strlen("Subject: ")+1;

    while (*s) {
        if (s[1] && string_isspace(s[0]) && !string_isspace(s[1])) {
            /* Linebreak on last LWS character before new word */
            if ((col + mywordlen(s)) >= 76) {
                bputs(b, CRLF" ");
                col=2;
                s++;
            }
        }
        bputc(b, *s++);
        col++;
    }
}

static void
draft_print_subject(struct buffer *b, struct pool *pool, char *s)
{
    BOOL encoded = NIL;

    s = draft_rfc1522_encode(pool, s, &encoded);

    bputs(b, "Subject: ");

    if (encoded) {
        /* rfc1522_encode() kicked in, with automatic line wrap */
        bputs(b, s);
    } else {
        /* Need to wrap original input string */
        draft_print_subject_fold(b, s); 
    }

    bputs(b, CRLF);
}

/* ====================================================================== */

/* draft_create() ********************************************************
 *
 * Create a new draft object. The base of the structure is allocated in
 * the session pool, however draft manages its own set of pools and
 * other data for message contents.
 ************************************************************************/

struct draft *draft_create(struct session *s)
{
    struct prefs *prefs = s->options->prefs;
    struct draft *d = pool_alloc(s->pool, sizeof(struct draft));

    d->have_draft = NIL;
    d->session = s;
    d->pool = NIL;
    d->role = NIL;
    d->from_personal = NIL;
    d->from_address = NIL;
    d->to = NIL;
    d->cc = NIL;
    d->bcc = NIL;
    d->fcc = NIL;
    d->reply_to = NIL;
    d->subject = NIL;
    d->body = NIL;
    d->in_reply_to = NIL;
    d->references = NIL;
    d->attlist = NIL;
    d->save_copy = prefs->use_sent_mail;
    d->line_wrap = prefs->line_wrap_on_send;
    d->reply_msgno = 0L;
    d->reply_msguid = 0L;
    d->reply_folder = NIL;
    d->rich_headers = NIL;

    return (d);
}

/* draft_free() **********************************************************
 *
 * Free the draft structure includes local memory pool.
 ************************************************************************/

void draft_free(struct draft *d)
{
    if (d->pool)
        pool_free(d->pool);

    d->pool = NIL;
    d->role = NIL;
    d->from_personal = NIL;
    d->from_address = NIL;
    d->to = NIL;
    d->cc = NIL;
    d->bcc = NIL;
    d->fcc = NIL;
    d->reply_to = NIL;
    d->subject = NIL;
    d->body = NIL;
    d->in_reply_to = NIL;
    d->references = NIL;
    d->attlist = NIL;
    d->save_copy = T;
    d->line_wrap = T;
    d->reply_msgno = 0L;
    d->reply_msguid = 0L;
    d->reply_folder = 0L;
    d->have_draft = NIL;
    d->rich_headers = NIL;
}

/* draft_copy() **********************************************************
 *
 * Copy draft structure. NB: doesn't copy contents, just moves existing
 * ptrs across.
 *   dst: Destination
 *   src: Source
 ************************************************************************/

void draft_copy(struct draft *dst, struct draft *src)
{
    memcpy(dst, src, sizeof(struct draft));
}

/* ====================================================================== */

/* draft_clear_hdrs() ****************************************************
 *
 * Clear headers
 *   restores reply to to default state
 ************************************************************************/

void draft_clear_hdrs(struct draft *d)
{
    struct prefs *prefs = d->session->options->prefs;

    d->to = NIL;
    d->cc = NIL;
    d->bcc = NIL;

    if (d->role && d->role->reply_to && d->role->reply_to[0])
        d->reply_to = d->role->reply_to;
    else
        d->reply_to = pool_strdup(d->pool, prefs->default_reply_to);

    if (d->role && d->role->fcc && d->role->fcc[0])
        d->fcc = d->role->fcc;
    else
        d->fcc = pool_strdup(d->pool, prefs->sent_mail_folder);

    d->subject = NIL;
}

/* draft_clear_body() ****************************************************
 *
 * Clear draft body, restorinfg initial signature for this role
 ************************************************************************/

void draft_clear_body(struct draft *d)
{
    struct session *session = d->session;
    struct prefs *prefs = session->options->prefs;
    char *s, *sig;

    if (d->role && d->role->signature && d->role->signature[0])
        sig = d->role->signature;
    else if (prefs->signature && prefs->signature[0])
        sig = prefs->signature;
    else {
        d->body = pool_strdup(d->pool, "");
        return;
    }

    /* Check whether signature already contains sigdash line */
    for (s = sig; *s; s++) {
        if ((s[0] == '-') && (s[1] == '-') && (s[2] == ' ') &&
            ((s[3] == '\015') || (s[3] == '\012'))) {
            d->body = pool_strcat(d->pool, "" CRLF "" CRLF, sig);
            return;
        }
    }

    /* Add sigdash to start of signature */
    d->body = pool_strcat(d->pool, "" CRLF "" CRLF "-- " CRLF, sig);
}

/* draft_init() **********************************************************
 *
 * Initialise new empty draft.
 ************************************************************************/

void draft_init(struct draft *d)
{
    struct session *session = d->session;
    struct prefs *prefs = session->options->prefs;

    if (d->pool)
        pool_free(d->pool);

    d->pool = pool_create(PREFERRED_DRAFT_BLOCK_SIZE);
    d->reply_msgno = 0L;
    d->reply_msguid = 0L;
    d->reply_folder = NIL;
    d->have_draft = T;
    d->save_copy = prefs->use_sent_mail;
    d->line_wrap = prefs->line_wrap_on_send;
    d->rich_headers = NIL;

    if (d->role && d->role->personal && d->role->personal[0])
        d->from_personal = pool_strdup(d->pool, d->role->personal);
    else if (prefs->from_personal)
        d->from_personal = pool_strdup(d->pool, prefs->from_personal);
    else
        d->from_personal = NIL;

    if (d->role && d->role->from && d->role->from[0])
        d->from_address = pool_strdup(d->pool, d->role->from);
    else if (prefs->from_address)
        d->from_address = pool_strdup(d->pool, prefs->from_address);
    else
        d->from_address = NIL;

    draft_clear_hdrs(d);
    draft_clear_body(d);
    draft_clear_atts(d);
}

/* draft_set_reply() *****************************************************
 *
 * Record information if this message is reply to some message
 ************************************************************************/

void
draft_set_reply(struct draft *draft,
                MAILSTREAM * stream, char *foldername, unsigned long msgno)
{
    struct session *session = draft->session;
    static char *short_hdrs[]
        = { "in-reply-to", "message-id", "references", NIL };
    static STRINGLIST *hdrslist = NIL;
    char *hdrs, *hdr, *s;
    unsigned long len;
    char *message_id = NIL;
    char *in_reply_to = NIL;
    char *references = NIL;

    /* One time only */
    if (!hdrslist)
        hdrslist = new_strlst(short_hdrs);

    draft->reply_validity = stream->uid_validity;
    draft->reply_msguid = ml_uid(session, stream, msgno);
    draft->reply_msgno = msgno;
    string_strdup(&draft->reply_folder, foldername);

    hdrs = ml_fetch_header(session, stream, msgno, NIL, hdrslist, &len, 0);
    if (hdrs) {
        hdrs = pool_strdup(draft->pool, hdrs); /* Scratch copy */

        while ((hdr = get_single_hdr(&hdrs)) != NULL) {
            if (!(s = strchr(hdr, ':')))
                continue;

            *s++ = '\0';
            while ((*s == ' ') || (*s == '\t'))
                s++;

            if (!strcasecmp(hdr, "Message-ID"))
                message_id = pool_strdup(draft->pool, s);
            else if (!strcasecmp(hdr, "In-Reply-To"))
                in_reply_to = pool_strdup(draft->pool, s);
            else if (!strcasecmp(hdr, "References"))
                references = pool_strdup(draft->pool, s);
        }
    }
    draft->in_reply_to = message_id;
    draft->references  = references;

    /* RFC 2822, section 3.6.4 says:
     *
     * The "References:" field will contain the contents of the parent's
     * "References:" field (if any) followed by the contents of the
     * parent's "Message-ID:" field (if any).
     *
     * If the parent message does not contain a "References:" field but
     * does have an "In-Reply-To:" field containing a single message
     * identifier, then the "References:" field will contain the contents
     * of the parent's "In-Reply-To:" field followed by the contents of the
     * parent's "Message-ID:" field (if any).
     *
     * If the parent has none of the "References:", "In-Reply-To:", or
     * "Message-ID:" fields, then the new message will have no
     * "References:" field.
     */

    if (references && references[0]) {
        if (message_id && message_id[0]) {
            draft->references = pool_printf(draft->pool, "%s %s",
                                            references, message_id);
        } else {
            draft->references = references;
        }
    } else if (in_reply_to && in_reply_to[0]) {
        if (message_id && message_id[0]) {
            draft->references = pool_printf(draft->pool, "%s %s",
                                            in_reply_to, message_id);
        } else {
            draft->references = in_reply_to;
        }
    } else {
        draft->references = message_id;
    }

    /* Pretty print references, one entry per line */
    if (draft->references && draft->references[0])
        draft->references = format_references(draft->pool, draft->references);
}

/* draft_clear_atts() ****************************************************
 *
 * Clear attachments from draft
 ************************************************************************/

void draft_clear_atts(struct draft *d)
{
    while (d->attlist)
        draft_delete_attachment(d, 1);
}

/* ====================================================================== */

/* draft_line_wrap_body() ************************************************
 *
 * Line wrap draft body using wrap() class.
 *         d: draft
 ************************************************************************/

void draft_line_wrap_body(struct draft *d)
{
    struct session *session = d->session;
    struct prefs *prefs = session->options->prefs;
    struct pool *p = pool_create(0);
    struct buffer *b = buffer_create(p, 0);     /* Temporary scratch buffer */

    wrap_paragraph(b, d->body, prefs->line_wrap_len);

    d->body =
        pool_strdup(d->pool, buffer_fetch(b, 0, buffer_size(b), NIL));
    pool_free(p);
}

/* ====================================================================== */

/* Auxiliary routine */

static char *pool_maybe_strdup(struct pool *pool, char *s)
{
    return ((s) ? pool_strdup(pool, s) : NIL);
}

/* Convert string into canonical CRLF format */

static char *
draft_canon_body(struct pool *pool, char *s)
{
    struct buffer *b = buffer_create(pool, 4096);

    while (*s) {
        if ((*s == '\015') || (*s == '\012')) {
            s += ((s[0] == '\015') && (s[1] == '\012')) ? 2 : 1;

            bputc(b, '\015');
            bputc(b, '\012');
        } else {
            bputc(b, *s);
            s++;
        }
    }

    return(buffer_fetch(b, 0, buffer_size(b), NIL));
}

/* draft_update_body() ***************************************************
 *
 * Update draft body into fresh pool, leaving headers intact
 *    d: Draft
 *    h: Form hash containing information about new draft.
 ************************************************************************/

void draft_update_body(struct draft *d, struct assoc *h)
{
    char *s;

    /* Create new pool */
    d->pool = pool_create(PREFERRED_DRAFT_BLOCK_SIZE);

    /* Duplicate Existing headers into new pool */
    d->from_personal = pool_maybe_strdup(d->pool, d->from_personal);
    d->from_address  = pool_maybe_strdup(d->pool, d->from_address);

    d->to = pool_maybe_strdup(d->pool, d->to);
    d->cc = pool_maybe_strdup(d->pool, d->cc);
    d->bcc = pool_maybe_strdup(d->pool, d->bcc);
    d->fcc = pool_maybe_strdup(d->pool, d->fcc);
    d->reply_to = pool_maybe_strdup(d->pool, d->reply_to);
    d->subject = pool_maybe_strdup(d->pool, d->subject);
    d->body = NIL;
    d->in_reply_to = pool_maybe_strdup(d->pool, d->in_reply_to);
    d->references = pool_maybe_strdup(d->pool, d->references);

    if ((s = assoc_lookup(h, "body"))) {
        d->body = draft_canon_body(d->pool, s);
    } else
        d->body = pool_strdup(d->pool, d->body);

    /*draft_transliterate_1252((unsigned char *)d->body);*/
}

/* draft_update() ********************************************************
 *
 * Update draft body and headers into fresh pool
 *    d: Draft
 *    h: Form hash containing new draft.
 ************************************************************************/

void draft_update(struct draft *d, struct assoc *h)
{
    struct session *session = d->session;
    struct prefs *prefs = session->options->prefs;
    char *s;

    d->pool = pool_create(PREFERRED_DRAFT_BLOCK_SIZE);

    d->from_personal = pool_maybe_strdup(d->pool, d->from_personal);
    d->from_address  = pool_maybe_strdup(d->pool, d->from_address);

    if ((s = assoc_lookup(h, "hdr_To")))
        d->to = pool_strdup(d->pool, s);
    else
        d->to = pool_maybe_strdup(d->pool, d->to);

    if ((s = assoc_lookup(h, "hdr_Cc")))
        d->cc = pool_strdup(d->pool, s);
    else
        d->cc = pool_maybe_strdup(d->pool, d->cc);

    if ((s = assoc_lookup(h, "hdr_Bcc")))
        d->bcc = pool_strdup(d->pool, s);
    else
        d->bcc = pool_maybe_strdup(d->pool, d->bcc);

    if ((s = assoc_lookup(h, "hdr_Fcc")))
        d->fcc = pool_strdup(d->pool, s);
    else
        d->fcc = pool_maybe_strdup(d->pool, d->fcc);

    if ((s = assoc_lookup(h, "hdr_Reply_To")))
        d->reply_to = pool_strdup(d->pool, s);
    else
        d->reply_to = pool_maybe_strdup(d->pool, d->reply_to);

    if ((s = assoc_lookup(h, "hdr_Subject")))
        d->subject = pool_strdup(d->pool, s);
    else
        d->subject = pool_maybe_strdup(d->pool, d->subject);

    d->save_copy = (assoc_lookup(h, "copy_outgoing") != NIL);

    if (prefs->line_wrap_advanced)
        d->line_wrap = (assoc_lookup(h, "line_wrap") != NIL);

    if ((s = assoc_lookup(h, "body")))
        d->body = draft_canon_body(d->pool, s);
    else
        d->body = pool_maybe_strdup(d->pool, d->body);

    /*draft_transliterate_1252((unsigned char *)d->body);*/

    d->in_reply_to = pool_maybe_strdup(d->pool, d->in_reply_to);
    d->references  = pool_maybe_strdup(d->pool, d->references);
}

/* ====================================================================== */

/* draft_add_to() ********************************************************
 *
 * Add to existing draft "To: " field.
 ************************************************************************/

void draft_add_to(struct draft *draft, char *s)
{
    s = string_trim_whitespace(s);

    if (draft->to && draft->to[0])
        draft->to = pool_strcat3(draft->pool, draft->to, ", ", s);
    else
        draft->to = pool_strdup(draft->pool, s);
}

/* draft_add_cc() ********************************************************
 *
 * Add to existing draft "Cc: " field.
 ************************************************************************/

void draft_add_cc(struct draft *draft, char *s)
{
    s = string_trim_whitespace(s);

    if (draft->cc && draft->cc[0])
        draft->cc = pool_strcat3(draft->pool, draft->cc, ", ", s);
    else
        draft->cc = pool_strdup(draft->pool, s);
}

/* draft_add_bcc() *******************************************************
 *
 * Add to existing draft "Bcc: " field.
 ************************************************************************/

void draft_add_bcc(struct draft *draft, char *s)
{
    s = string_trim_whitespace(s);

    if (draft->bcc && draft->bcc[0])
        draft->bcc = pool_strcat3(draft->pool, draft->bcc, ", ", s);
    else
        draft->bcc = pool_strdup(draft->pool, s);
}

/* ====================================================================== */

/* draft_add_attachment() ************************************************
 *
 * Add attachment to draft.
 *        d: Draft
 *     name: Name of attachment
 *     type: Type of attachment
 * encoding: Encoding of attachment.
 *      hdr: Header block associated with attachment
 * hdr_size:
 *      msg: Msg body associated with attachment.
 * msg_size:
 ************************************************************************/

void
draft_add_attachment(struct draft *d,
                     char *name, char *type, char *encoding,
                     char *hdr, unsigned long hdr_size,
                     char *msg, unsigned long msg_size)
{
    struct attlist *last, *new;

    new = pool_alloc(NIL, sizeof(struct attlist));
    new->next = NIL;
    new->name = (name) ? pool_strdup(NIL, name) : NIL;
    new->type = (type) ? pool_strdup(NIL, type) : NIL;
    new->encoding = (encoding) ? pool_strdup(NIL, encoding) : NIL;

    if (hdr) {
        new->hdr = pool_alloc(NIL, hdr_size);
        new->hdr_size = hdr_size;
        memcpy(new->hdr, hdr, hdr_size);
    } else {
        new->hdr = NIL;
        new->hdr_size = 0;
    }

    new->body = pool_alloc(NIL, msg_size);
    new->size = msg_size;
    memcpy(new->body, msg, msg_size);

    if ((last = d->attlist)) {
        while (last->next != NIL)
            last = last->next;
        last->next = new;
    } else
        d->attlist = new;
}

/* draft_delete_attachment() *********************************************
 *
 * Deleted numbered attachement from draft
 *       d: Draft
 *  number: Number of attachment to delete.
 ************************************************************************/

void draft_delete_attachment(struct draft *d, unsigned long number)
{
    struct attlist *current, *p;
    unsigned long count;

    if ((number < 1) || (d->attlist == NIL))
        return;

    /* Remove link from chain */
    if (number > 1) {
        count = 2;
        current = d->attlist;

        while (count < number) {
            if (current->next == NIL)
                break;
            current = current->next;
            count++;
        }

        if (current->next == NIL)
            return;

        p = current->next;
        current->next = p->next;
    } else {
        p = d->attlist;
        d->attlist = p->next;
    }

    /* Free data associated with link */
    if (p->name)
        free(p->name);
    if (p->type)
        free(p->type);
    if (p->encoding)
        free(p->encoding);
    if (p->hdr)
        free(p->hdr);
    if (p->body)
        free(p->body);
    free(p);
}

/* ====================================================================== */

/* draft_att_count() *****************************************************
 *
 * Return current attachment count
 *    d: draft
 *
 * Returns: Attachment count.
 ************************************************************************/

unsigned long draft_att_count(struct draft *d)
{
    struct attlist *current;
    unsigned long count = 0;

    for (current = d->attlist; current; current = current->next)
        count++;

    return (count);
}

/* draft_att_size() *******************************************************
 *
 * Return total size of attachments in current message.
 *    d: draft
 *
 * Returns: Attachment count.
 ************************************************************************/

unsigned long draft_att_size(struct draft *d)
{
    struct attlist *current;
    unsigned long size = 0;

    for (current = d->attlist; current; current = current->next)
        size += current->size;

    return (size);
}

/* ====================================================================== */

/* draft_role_set() *******************************************************
 *
 * Set role for this draft. NB: relies on fact that role cannot be changed
 * or deleted while draft is active.
 *************************************************************************/

void draft_role_set(struct draft *d, struct role *role)
{
    d->role = role;
}

/* draft_rich_headers() **************************************************
 *
 * Enable or disable rich headers.
 *       d: Draft
 *  enable: T => enable. NIL => disable
 ************************************************************************/

void draft_rich_headers(struct draft *d, BOOL enable)
{
    d->rich_headers = enable;
}

/* draft_init_rich_headers() **********************************************
 *
 * Initialise rich header glag: enable if reply to or bcc is set.
 *************************************************************************/

void draft_init_rich_headers(struct draft *d)
{
    if (d->bcc && d->bcc[0])
        d->rich_headers = T;
    else if (d->reply_to && d->reply_to[0])
        d->rich_headers = T;
    else
        d->rich_headers = NIL;
}

/* ====================================================================== */

/* draft_make_recip() ****************************************************
 *
 * Convert header field into (SMTP level, un-annotated) list of recipients
 * in output buffer following addressbook expansion
 *   session:
 *        rb: Output buffer for list of recipients
 *    string: String to expand.
 *    countp: Running total for number of recipients
 *
 * Returns:   T on sucess
 *          NIL if error parsing header fields
 ************************************************************************/

static BOOL
draft_make_recip(struct session *session, struct buffer *rb,
                 char *string, unsigned long *countp)
{
    struct options *options = session->options;
    struct prefs *prefs = options->prefs;
    char *default_domain = prefs->default_domain;
    struct abook *abook = session->options->abook;
    ADDRESS *addr = NIL, *a;
    char *text;
    unsigned long recips = 0;

    /* Quietly ignore empty fields */
    if (!(string && string[0]))
        return (T);

    /* Do abook lookup. */
    if (!(text = abook_substitute(session, rb->pool, abook, string)))
        return (NIL);

    if (!(addr=addr_parse_destructive(text, default_domain))) {
        session_message(session, "%s", string, ml_errmsg());
        return (NIL);
    }

    /* Non empty address */
    for (a = addr; a; a = a->next) {
        bprintf(rb, " %s@%s", a->mailbox, a->host);
        recips++;
    }

    if (countp)
        (*countp) += recips;

    mail_free_address(&addr);
    return (T);
}

/* draft_make_recipients() ************************************************
 *
 * Generate (SMTP level, un-annotated) list of recipients from To:, Cc:
 * and Bcc: headers following addressbook expansion.
 *     draft: Current draft.
 *        rb: Output buffer for list of recipients
 *       len: Length of result returned here if not NIL.
 *    countp: Number of recipients returned here.
 *
 * Returns: NULL terminated string allocated from session->request->pool
 *          NIL if error parsing header fields
 ************************************************************************/

char *draft_make_recipients(struct draft *draft,
                            unsigned long *len,
                            unsigned long *countp)
{
    struct session *session = draft->session;
    struct pool *pool = session->request->pool;
    struct buffer *rb = buffer_create(pool, 4096);

    if (countp)
        *countp = 0;

    if (!(draft_make_recip(session, rb, draft->to, countp) &&
          draft_make_recip(session, rb, draft->cc, countp) &&
          draft_make_recip(session, rb, draft->bcc, countp)))
        return (NIL);

    if (len)
        *len = buffer_size(rb);

    return (buffer_fetch(rb, 0, buffer_size(rb), NIL));
}

/* ====================================================================== */

/* draft_make_addr() ****************************************************
 *
 * Convert single address item into RFC822 format recipient, including
 * nasty charset munging
 *   session:
 *       str: Scratch str.
 *  personal: Personal component of recipient
 *   mailbox: Mailbox  component of recipient
 *      host: Host     component of recipient
 *
 * Returns: Combined string
 ************************************************************************/

static char *
draft_make_addr(struct str *str, struct pool *pool,
                char *personal, char *mailbox, char *host)
{
    str_rewind(str, 0);

    if (personal && personal[0]) {
        if (string_has8bit(personal)) {
            str_puts(str, draft_rfc1522_encode(pool, personal, NIL));
        } else if (string_atom_has_special(personal)) {
            str_printf(str, "\"%s\"", string_atom_quote(pool, personal));
        } else {
            str_puts(str, personal);
        }

        if (mailbox && mailbox[0] && host && host[0]) {
            str_printf(str, " <%s@%s>", mailbox, host);
        } else if (mailbox && mailbox[0]) {
            str_printf(str, " <%s>", mailbox);
        } else if (host && host[0]) {
            str_printf(str, " <%s>", host);
        }
    } else if (mailbox && mailbox[0] && host && host[0]) {
        str_printf(str, "%s@%s", mailbox, host);
    } else if (mailbox && mailbox[0]) {
        str_puts(str, mailbox);
    } else if (host && host[0]) {
        str_puts(str, mailbox);
    }

    return (str_fetch(str));
}

/* ====================================================================== */

/* draft_make_address() **************************************************
 *
 * Expand and quote single header field into RFC822 format
 *    session:
 *         mb: Target buffer
 *        hdr: Header (e.g: "To: " or "Cc: "
 *       text: Text to parse and expand.
 ************************************************************************/

static BOOL
draft_make_address(struct session *session,
                   struct buffer *mb, char *hdr, char *text)
{
    ADDRESS *a, *addr = NIL;
    struct request *request = session->request;
    struct pool    *pool    = request->pool;
    struct str     *tmpstr  = str_create(request->pool, 0);
    int offset = 0;
    int count = 0;
    int i;

    /* Quietly ignore empty fields */
    if (!(text && text[0]))
        return (T);

    if (!(addr=addr_parse_destructive(text, ""))) { /* Okay to trash text */
        session_message(session, "%s", ml_errmsg());
        return (NIL);
    }

    bprintf(mb, "%s: ", hdr);
    offset = strlen(hdr) + strlen(": ");

    for (a = addr; a; a = a->next) {
        char *s = draft_make_addr(tmpstr, pool, 
                                  a->personal, a->mailbox, a->host);
        int len = strlen(s);

        if (count > 0) {
            /* ',' separator, with potential line fold */
            if ((offset+len+2) >= 78) {
                bputs(mb, "," CRLF);

                //offset = strlen(hdr) + strlen(": ");
                offset = 4;
                for (i = 0; i < offset; i++)
                    bputc(mb, ' ');
            } else {
                bputs(mb, ", ");
                offset += 2;
            }
        }

        bputs(mb, s);
        offset += len;
        count++;
    }
    bputs(mb, "" CRLF);
    mail_free_address(&addr);
    return (T);
}

/* draft_make_abook_address() ********************************************
 *
 * Expand and quote single header field into RFC822 format, with
 * addressbook expansion.
 *    session:
 *         mb: Target buffer
 *        hdr: Header (e.g: "To: " or "Cc: "
 *       text: Text to parse and expand.
 ************************************************************************/

static BOOL
draft_make_abook_address(struct session *session,
                         struct buffer *mb, char *hdr, char *text)
{
    struct abook *abook = session->options->abook;

    /* Quietly ignore empty fields */
    if (!(text && text[0]))
        return (T);

    /* Do abook lookup. */
    if (!(text = abook_substitute(session, mb->pool, abook, text)))
        return (NIL);

    /* Redundancy here: draft_make_address() reparses address 
     * Should abook_substitute() return ADDRESS list? */

    draft_make_address(session, mb, hdr, text);
    return (T);
}


/* ====================================================================== */

/* Convert 8BIT contents to QUOTED-PRINTABLE
 * Code stolen from c-client: rfc822/rfc822_8bit().
 */

#define MAXL (size_t) 75        /* 76th position only used by continuation = */
#define MAXL_FORCE_QPRINT (900) /* Force qprint with very long lines */

/* draft_need_qprint() ***************************************************
 *
 * Check whether string contains 8 bit characters or _very_ long lines
 * which would require QP encoding.
 ************************************************************************/

static BOOL draft_need_qprint(char *src0)
{
    unsigned char *src = (unsigned char *) src0;
    unsigned long lp = 0;
    unsigned char c;

    while (*src) {              /* for each character */
        /* true line break? */
        if ((*src == '\015') || (*src == '\012')) {
            src += ((src[0] == '\015') && (src[1] == '\012')) ? 2 : 1;
            lp = 0;             /* reset line count */
        } else {                /* not a line break */
            c = *src++;

            /* quoting required if special characters in play */
            if (Uiscntrl(c) || (c == 0x7f) || (c & 0x80))
                return (T);

            /* Ordinary character: would line overflow? */
            if ((++lp) > MAXL_FORCE_QPRINT)
                return (T);
        }
    }
    return (NIL);
}

/* draft_make_qprint() ***************************************************
 *
 * Convert string into quoted-printable representation.
 *      b: Target buffer
 *   src0: Source string to convert.
 ************************************************************************/

static void draft_make_qprint(struct buffer *b, char *src0)
{
    unsigned char *src = (unsigned char *) src0;
    unsigned long lp = 0;
    char *hex = "0123456789ABCDEF";
    unsigned char c;

    while (*src) {              /* for each character */
        /* true line break? */
        if ((*src == '\015') || (*src == '\012')) {
            src += ((src[0] == '\015') && (src[1] == '\012')) ? 2 : 1;
            bputc(b, '\015');
            bputc(b, '\012');
            lp = 0;             /* reset line count */
        } else {                /* not a line break */
            /* quoting required? */
            c = *src++;

            if (iscntrl(c) || (c == 0x7f) || (c & 0x80) || (c == '=') ||
                ((c == ' ') && (*src == '\015'))) {
                if ((lp += 3) > MAXL) { /* yes, would line overflow? */
                    bputc(b, '=');
                    bputc(b, '\015');
                    bputc(b, '\012');
                    lp = 3;     /* set line count */
                }
                bputc(b, '=');  /* quote character */
                bputc(b, hex[c >> 4]);  /* high order 4 bits */
                bputc(b, hex[c & 0xf]); /* low order 4 bits */
            } else {            /* ordinary character */
                if ((++lp) > MAXL) {    /* would line overflow? */
                    bputc(b, '=');
                    bputc(b, '\015');
                    bputc(b, '\012');
                    lp = 1;     /* set line count */
                }
                bputc(b, c);    /* ordinary character */
            }
        }
    }
}

/* draft_make_normal() ***************************************************
 *
 * Convert string into canonical format for draft.
 *      b: Target buffer
 *   src0: Source string to convert.
 ************************************************************************/

static void draft_make_normal(struct buffer *b, char *src0)
{
    unsigned char *s = (unsigned char *) src0;

    while (*s) {
        if ((*s == '\015') || (*s == '\012')) {
            s += ((s[0] == '\015') && (s[1] == '\012')) ? 2 : 1;

            bputc(b, '\015');
            bputc(b, '\012');
        } else {
            bputc(b, *s);
            s++;
        }
    }
}

/* ====================================================================== */

/* draft_make_multipart() *************************************************
 *
 * Convert draft with attachments into multipart MIME message.  Static
 * support routine for draft_make_msg().
 *        draft:
 *            b: Target buffer
 *
 * NB: RFC 2046 section 5.1.1 indicates that initial CRLF should be
 *     counted as part of separator line, not attachment body
 *************************************************************************/

static void draft_make_multipart(struct draft *draft, struct buffer *b)
{
    struct attlist *attlist = draft->attlist;
    char *s = draft->body;
    BOOL draft_is_iso_8859_1 = utf8_is_8859_1(s);
    BOOL need_qprint = NIL;
    unsigned long len;
    char cookie[80];

    /* Boundary algorithmn stolen c-client */
    sprintf(cookie, "-%ld-%ld-%ld=:%ld",
            (long) gethostid(), random(), time(0), (long) getpid());

    if (draft_is_iso_8859_1) {
        s = pool_strdup(draft->pool, s);
        utf8_to_8859_1(s);
    }

    if (s && s[0] && draft_need_qprint(s))
        need_qprint = T;

    bprintf(b, "Content-Type: multipart/mixed; boundary=\"%s\"" CRLF,
            cookie);
    bputs(b, "" CRLF);

    /* First line */
    bputs(b, "  This message is in MIME format.");
    bputs(b, "  The first part should be readable text," CRLF);

    /* Second line */
    bputs(b, "  while the remaining parts are likely unreadable ");
    bputs(b, "without MIME-aware tools." CRLF);

    /* Third line */
    bputs(b, "  Send mail to mime@docserver.cac.washington.edu ");
    bputs(b, "for more info." CRLF);

    /* Message body */
    bprintf(b, CRLF "--%s" CRLF, cookie);

    bprintf(b, "Content-Type: text/plain; format=flowed; charset=%s"CRLF,
            (draft_is_iso_8859_1) ? "ISO-8859-1" : "UTF-8");

    if (need_qprint)
        bputs(b, "Content-Transfer-Encoding: QUOTED-PRINTABLE" CRLF);

    bprintf(b, "" CRLF);

    if (s  && s[0]) {
        if (need_qprint)
            draft_make_qprint(b, s);
        else
            draft_make_normal(b, s);
    }

    while (attlist) {
        bprintf(b, CRLF "--%s" CRLF, cookie);

        if (attlist->hdr) {
            for (s = attlist->hdr, len = attlist->hdr_size; len > 0;
                 s++, len--)
                bputc(b, *s);

            for (s = attlist->body, len = attlist->size; len > 0; len--)
                bputc(b, *s++);
        } else {
            char *type;
            char *name = attlist->name;

            if (attlist->type && attlist->type[0])
                type = attlist->type;
            else
                type = "application/octet-stream";

            if (name && name[0]) {
                if (string_has8bit(name) ||
                    strchr(name, '"') ||
                    strlen(name) > 78) {
                    /* Need RFC 2231 encoding */

                    if (utf8_is_8859_1(name)) {
                        name=pool_strdup(draft->pool, name);
                        utf8_to_8859_1(name);
                        name = string_url_encode(draft->pool, name);
                        name = pool_printf(draft->pool,
                                           "ISO-8859-1''%s", name);
                    } else {
                        name = string_url_encode(draft->pool, name);
                        name = pool_printf(draft->pool,
                                           "UTF-8''%s", name);
                    }

                    bprintf(b, "Content-Type: %s; name*=%s"CRLF,
                            type, name);
                    bprintf(b,
                            "Content-Disposition: attachment; filename*=%s"CRLF,
                            name);
                    bprintf(b, "Content-Description: "CRLF);

                } else if (strpbrk(name, " \t()<>@,;:\\/[]?=")) {
                    /* RFC 2183 (Section 2) lists characters which must be
                     * quoted. '"' is handled above: could be quoted as \",
                     * (rfc822 "quoted-string"), but RFC 2231 encoding feels
                     * safer to me.
                     */
                    bprintf(b, "Content-Type: %s; name=\"%s\"" CRLF,
                            type, name);
                    bprintf(b,
                            "Content-Disposition: attachment; filename=\"%s\""
                            CRLF, name);
                    bprintf(b, "Content-Description: %s" CRLF, name);
                } else {
                    bprintf(b, "Content-Type: %s; name=%s" CRLF, type,
                            name);
                    bprintf(b,
                            "Content-Disposition: attachment; filename=%s"
                            CRLF, name);
                    bprintf(b, "Content-Description: %s" CRLF, name);
                }
            } else
                bprintf(b, "Content-Type: %s" CRLF, type);

            if (!strcasecmp(type, "message/rfc822")) {
                /* RFC2046 states: No encoding other than "7bit", "8bit", or
                 * "binary" is permitted for the body of a "message/rfc822" entity.
                 * The message header fields are always US-ASCII in any case, and
                 * data within the body can still be encoded, in which case the
                 * Content-Transfer-Encoding header field in the encapsulated
                 * message will reflect this.  Non-US-ASCII text in the headers of
                 * an encapsulated message can be specified using the mechanisms
                 * described in RFC 2047.
                 *
                 * Consequently we need to strip off any base64/qprint encoding
                 * from the upload process and send the attachment as raw data.
                 */
                bputs(b, "Content-Transfer-Encoding: binary" CRLF);
                bputs(b, "" CRLF);

                if (attlist->body) {
                    char *decode_msg = attlist->body;
                    unsigned long decode_len = attlist->size;

                    if (attlist->encoding && attlist->encoding[0]) {
                        /* Strip off BASE64/QPRINT encoding */
                        if (!strcasecmp(attlist->encoding, "BASE64")) {
                            decode_msg
                                = (char *) rfc822_base64((unsigned char *)
                                                         attlist->body,
                                                         attlist->size,
                                                         &decode_len);

                        } else
                            if (!strcasecmp
                                (attlist->encoding, "QUOTED-PRINTABLE")) {
                            decode_msg
                                = (char *) rfc822_qprint((unsigned char *)
                                                         attlist->body,
                                                         attlist->size,
                                                         &decode_len);
                        }
                    }

                    if (decode_msg) {
                        for (s = decode_msg, len = decode_len; len > 0;
                             len--)
                            bputc(b, *s++);

                        if (decode_msg != attlist->body)
                            fs_give((void **) &decode_msg);
                    }

                }
            } else if (attlist->encoding && attlist->encoding[0]) {
                /* Not Message/RFC822 and transfer encoding was specified */
                bprintf(b, "Content-Transfer-Encoding: %s" CRLF,
                        attlist->encoding);
                bputs(b, "" CRLF);

                if (attlist->body) {
                    for (s = attlist->body, len = attlist->size; len > 0;
                         len--)
                        bputc(b, *s++);
                }
            } else {
                /* Default to BASE64 for attachmnents as typically binary data */
                bputs(b, "Content-Transfer-Encoding: BASE64" CRLF);
                bputs(b, "" CRLF);

                if (attlist->body) {
                    unsigned long b64len;
                    char *base64 = (char *) rfc822_binary(attlist->body,
                                                          attlist->size,
                                                          &b64len);

                    if (b64len > 2) {
                        /* Print without trailing CRLF */
                        for (s = base64, len = (b64len - 2); len > 0;
                             len--)
                            bputc(b, *s++);
                    }
                    fs_give((void **) &base64);
                }
            }
        }

        attlist = attlist->next;
    }
    bprintf(b, CRLF "--%s--" CRLF, cookie);
}

/* ====================================================================== */

/* draft_make_single_part() ***********************************************
 *
 * Convert draft with no attachments into simple message.  Static support
 * routine for draft_make_msg().
 *        draft:
 *            b: Target buffer
 *************************************************************************/

static void draft_make_single_part(struct draft *draft, struct buffer *b)
{
    char *s = draft->body;
    BOOL need_qprint = NIL;
    BOOL draft_is_iso_8859_1 = utf8_is_8859_1(s);

    if (draft_is_iso_8859_1) {
        s = pool_strdup(draft->pool, s);
        utf8_to_8859_1(s);
    }

    if (s && s[0] && draft_need_qprint(s))
        need_qprint = T;

    bprintf(b, "Content-Type: text/plain; format=flowed; charset=%s"CRLF,
            (draft_is_iso_8859_1) ? "ISO-8859-1" : "UTF-8");
          
    if (need_qprint)
        bputs(b, "Content-Transfer-Encoding: quoted-printable" CRLF);

    bputs(b, "" CRLF);
    if (s && s[0]) {
        if (need_qprint)
            draft_make_qprint(b, s);
        else
            draft_make_normal(b, s);
    }
    bputs(b, "" CRLF);
}

/* ====================================================================== */

/* draft_make_msg() ******************************************************
 *
 * Convert draft into RFC822 format message suitable for SMTP or 
 * IMAP append.
 *      draft:
 *   postpone: Making postpone message (record Fcc header, reply info)
 *        len:  Returns length of resulting message if non-NIL.
 *
 * Returns: NULL terminated string allocated from session->request->pool.
 ************************************************************************/

char *draft_make_msg(struct draft *draft, BOOL postpone,
                     unsigned long *len)
{
    struct session *session = draft->session;
    struct config *config = session->config;
    struct options *options = session->options;
    struct prefs *prefs = options->prefs;
    char *default_domain = prefs->default_domain;
    struct pool *pool = session->request->pool;
    struct buffer *mb = buffer_create(pool, 4096);
    struct str *str = str_create(pool, 0);
    char *s, *value;
    char *from_address, *from_personal = NIL;
    BOOL use_sender = NIL;
    char date[64];

    rfc822_fixed_date(date);        /* Generates fixed length output */

    if (draft->from_address && draft->from_address[0]) {
        from_address = draft->from_address;
        use_sender = T;
    } else if (strchr(session->username, '@')) {
        from_address = pool_strdup(pool, session->username);
    } else {
        from_address =
            pool_printf(pool, "%s@%s", session->username, default_domain);
    }

    if (draft->from_personal && draft->from_personal[0]) {
        from_personal = draft->from_personal;
    } else if (config->local_domain_list) {
        struct list_item *li;

        for (li = config->local_domain_list->head; li; li = li->next) {
            struct config_local_domain *cld =
                (struct config_local_domain *) li;

            if (cld->cdb_map && !strcmp(default_domain, cld->name) &&
                cdb_find(cld->cdb_map,
                         session->username, strlen(session->username),
                         &value)
                && value && value[0]
                && (value = string_trim_whitespace(value))) {
                from_personal = pool_strdup(pool, value);
                free(value);
                break;
            }
        }
    }

    /* Order of headers according to the note in section 4.1 of RFC 822 */

    if (!postpone) {
        bprintf(mb,
	    "Received: from [%s] by %s" CRLF
	    "	with HTTP (Prayer-%s); %s" CRLF,
	    ipaddr_text(session->ipaddr),
	    config->hostname,
	    VERSION_PRAYER, date);
    }

    bprintf(mb, "Date: %s" CRLF, date);
    bprintf(mb, "From: %s" CRLF,
            draft_make_addr(str, pool, from_personal, from_address, NIL));

    if (use_sender) {
        if (strchr(session->username, '@')) {
            bprintf(mb, "Sender: %s" CRLF, session->username);
        } else {
            bprintf(mb, "Sender: %s@%s" CRLF, session->username,
                    default_domain);
        }
    }

    if (!(draft_make_abook_address(session, mb, "To", draft->to) &&
          draft_make_abook_address(session, mb, "Cc", draft->cc) &&
          draft_make_abook_address(session, mb, "Bcc", draft->bcc)))
        return (NIL);

    if ((s = draft->reply_to) && s[0])
        draft_make_address(session, mb, "Reply-To", s);

    if ((s = draft->subject) && s[0])
        draft_print_subject(mb, pool, s);

    if (draft->fcc && draft->fcc[0] && !string_filename_valid(draft->fcc)) {
        session_message(session, "Invalid Fcc name");
        return (NIL);
    }

    if (postpone) {
        if (draft->fcc && draft->fcc[0])
            bprintf(mb, "Fcc: %s" CRLF, draft->fcc);

        if (draft->reply_msguid && draft->reply_folder) {
            char *name =
                session_mailbox(session, pool, draft->reply_folder);

            /* Record message that we are replying to in approx Pine format */

            bprintf(mb, "X-Reply-UID: (2 > )(1 %lu %lu)%s" CRLF,
                    draft->reply_validity, draft->reply_msguid, name);

            bprintf(mb, "X-Reply-Mbox: %s" CRLF, name);
        }

        /* PINE ignores Reply-To unless the following are present */
        if ((s = draft->reply_to) && s[0]) {
            bputs(mb, "X-Our-Headers: Reply-To"CRLF);
            bputs(mb, "X-Our-ReplyTo: Full"CRLF);
        }
    }

    bprintf(mb, "Message-ID: %s"CRLF, generate_message_id(config));

    if (draft->in_reply_to && draft->in_reply_to[0])
        bprintf(mb, "In-Reply-To: %s"CRLF, draft->in_reply_to);

    if (draft->references && draft->references[0])
        bprintf(mb, "References: %s"CRLF, draft->references);

    if (!postpone)
        bprintf(mb, "X-Mailer: Prayer v%s" CRLF, VERSION_PRAYER);

    bprintf(mb, "Mime-Version: 1.0" CRLF);

    if (draft->attlist)
        draft_make_multipart(draft, mb);
    else
        draft_make_single_part(draft, mb);

    if (len)
        *len = buffer_size(mb);

    return (buffer_fetch(mb, 0, buffer_size(mb), NIL));
}

/* ====================================================================== */

/* Interface for restoring draft messages from postponed message folder */

/* Static support routine for draft_restore_postponed */

static BOOL
add_attachments(struct draft *draft,
                struct pool *pool, MAILSTREAM * stream,
                unsigned long msgno)
{
    struct session *session = draft->session;
    BODY *body;
    PART *part;
    unsigned long section;

    if (!ml_fetch_structure(session, stream, msgno, &body, NIL))
        return (NIL);

    /* Check for multipart mesage with at least two parts */
    if (!((body->type == TYPEMULTIPART) &&
          (part = body->nested.part) && (part = part->next))) {
        return (T);;
    }

    for (section = 2; part != NIL; part = part->next, section++) {
        char *stext = string_itoa(pool, section);
        char *hdr, *msg;
        unsigned long hdr_len, msg_len;
        char *name, *type, *encoding;
        PARAMETER *parameter;

        body = &part->body;

        name = "";
        for (parameter = body->parameter; parameter;
             parameter = parameter->next) {
            if (!strcasecmp(parameter->attribute, "NAME"))
                name = parameter->value;
        }
        type =
            pool_strcat3(pool, body_types[body->type], "/", body->subtype);
        encoding = pool_strdup(pool, body_encodings[body->encoding]);

        string_lcase(type);
        string_lcase(encoding);

        if (!
            ((hdr =
              ml_fetch_mime(session, stream, msgno, stext, &hdr_len,
                            FT_PEEK))
             && (msg =
                 ml_fetchbody(session, stream, msgno, stext, &msg_len))))
            return (NIL);

        draft_add_attachment(draft, name, type, encoding,
                             hdr, hdr_len, msg, msg_len);
    }
    return (T);
}

/* ====================================================================== */

/* Cut up X-Reply-UID header, which has the following format
 *
 *  (2 > )(1 1017307523 57135)/home_0/dpc22/inbox  <-- mboxname
 *   ^^^   ^     ^       ^
 *    |    |     |       +--- Message UID
 *    |    |     +----------- UID validity
 *    |    +----------------- Unknown
 *    +---------------------- Unknown
 *
 *
 * mboxname can have any of the following formats:
 *   /home_0/dpc22/mail/fred  -- absolute folder name
 *   mail/fred                -- relative folder name
 *   {...}mail/fred           -- IMAP folder name
 *
 * Make best approximation that we can. Isn't C fun for regexp matching...
 *
 */

static BOOL draft_parse_reply_uid_worker(struct draft *draft, char *s)
{
    struct session *session = draft->session;
    unsigned long uidvalidity;
    unsigned long uid;

    if (strncmp(s, "(2 > )(1 ", strlen("(2 > )(1 ")) != 0)
        return (NIL);

    s += strlen("(2 > )(1 ");

    while (Uisspace(*s))
        s++;

    if (!Uisdigit(*s))
        return (NIL);

    uidvalidity = atoi(s);

    while (Uisdigit(*s))
        s++;

    if (!Uisspace(*s))
        return (NIL);

    while (Uisspace(*s))
        s++;

    if (!Uisdigit(*s))
        return (NIL);

    uid = atoi(s);

    while (Uisdigit(*s))
        s++;

    if (*s != ')')
        return (NIL);

    s++;

    /* Skip over remote folder stuff */
    if (*s == '{') {
        if (!(s = strchr(s, '}')))
            return (NIL);
        s++;
    }

    /* Skip over /home/userid or /home_xx/userid */
    if (!strncmp(s, "/home", strlen("/home"))) {
        if (!(s = strchr(s, '/')))
            return (NIL);
        s++;

        if (strncmp(s, session->username, strlen(session->username)) != 0)
            return (NIL);
        s += strlen(session->username);

        if (*s != '/')
            return (NIL);
        s++;
    }

    /* Got a match: record the message that we are responding to */

    draft->reply_validity = uidvalidity;
    draft->reply_msguid = uid;
    draft->reply_msgno = 0;     /* Unknown: calculate from UID */
    string_strdup(&draft->reply_folder, s);

    return (T);
}

static void draft_parse_reply_uid(struct draft *draft, char *s)
{
    struct session *session = draft->session;

    if (!draft_parse_reply_uid_worker(draft, s))
        session_log(session,
                    ("[draft_parse_reply_uid] "
                     "Failed to parse X-Reply-UID headers: %s"), s);
}

/* ====================================================================== */

/* Extract useful bit from sent-mail name */

#define IMAPURL_PREFIX "imap://"

static char *draft_parse_fcc(char *fcc)
{
    char *t;

    /* Match imap://user@imap.hermes.cam.ac.uk/<name> */
    if (!strncmp(fcc, IMAPURL_PREFIX, strlen(IMAPURL_PREFIX)) &&
        (t=strchr(fcc+strlen(IMAPURL_PREFIX)+1, '/')))
        return(t+1);

    /* Match {imap.hermes.cam.ac.uk}<name> */
    if ((t = strrchr(fcc, '}')) != NIL)
        return(t+1);

    /* Take anything else as is */
    return(fcc);
}

/* draft_restored_postponed() ********************************************
 *
 * Generate draft message from message in postponed folder, including any
 * attachments.
 *        d: Draft
 *     pool: Scratch pool
 *   stream: Postponed messages stream
 *    msgno: Message in postponed messages stream.
 *
 * Returns:  T if message restored successfully. NIL otherwise.
 ************************************************************************/

BOOL
draft_restore_postponed(struct draft *d,
                        struct pool *pool, MAILSTREAM * stream, long msgno)
{
    struct session *session = d->session;
    MESSAGECACHE *elt;
    ENVELOPE *env;
    BODY *body = NIL;
    char *init_msg;
    char *decode_msg;
    char *hdrs, *hdr, *s;
    char *charset = "ISO-8859-1";
    PARAMETER *parameter;
    unsigned long len;
    static STRINGLIST *fcc_hdrslist = NIL;
    static char *short_hdrs[] = {"fcc", "x-reply-uid", "x-reply-mbox",
                                 "in-reply-to", "references", NIL };

    /* One time only */
    if (!fcc_hdrslist)
        fcc_hdrslist = new_strlst(short_hdrs);

    /* Check that message is in range */
    if ((msgno == 0) || (msgno > stream->nmsgs)) {
        session_message(session, "Postponed message %lu no longer exists",
                        msgno);
        session_log(session,
                    ("[draft_restore_postponed]"
                     "Postponed message %lu no longer exists"),
                    msgno);
        return (NIL);
    }

    if (!((elt = ml_elt(session, stream, msgno)) &&
          (env = ml_fetch_structure(session, stream, msgno, NIL, 0))))
        return (NIL);

    /* Restore hdrs */

    if (env->from) {
        ADDRESS *a = env->from;

        if (a->mailbox && a->host)
            d->from_address = pool_printf(d->pool, "%s@%s",
                                          a->mailbox, a->host);

        if (a->personal) {
            char *s = pool_strdup(d->pool, a->personal);
            void *t = pool_alloc(d->pool, strlen(s));

            d->from_personal
                = (char *) rfc1522_decode(t, strlen(s), s, NIL);
        }

        /* XXX Temporary: Just to confirm sensible headers */
        session_log(session,
                    ("[draft_restore_postponed] From address: %s <%s>"),
                    (d->from_personal) ? d->from_personal : "",
                    (d->from_address) ? d->from_address : "");
    }

    if (env->to) {
        d->to = pool_strdup(d->pool, addr_text(pool, env->to));

        d->to = (char *) rfc1522_decode(pool_alloc(pool, strlen(d->to)),
                                        strlen(d->to), d->to, NIL);
    } else
        d->to = pool_strdup(d->pool, "");

    if (env->cc) {
        d->cc = pool_strdup(d->pool, addr_text(pool, env->cc));
        d->cc = (char *) rfc1522_decode(pool_alloc(pool, strlen(d->cc)),
                                        strlen(d->cc), d->cc, NIL);
    } else
        d->cc = pool_strdup(d->pool, "");

    if (env->bcc) {
        d->bcc = pool_strdup(d->pool, addr_text(pool, env->bcc));
        d->bcc = (char *) rfc1522_decode(pool_alloc(pool, strlen(d->bcc)),
                                         strlen(d->bcc), d->bcc, NIL);
    } else
        d->bcc = pool_strdup(d->pool, "");

    if (env->from && env->reply_to) {
        ADDRESS *a = env->from;
        ADDRESS *b = env->reply_to;

        while (a && b) {
            if (!
                (a->mailbox && b->mailbox
                 && !strcmp(a->mailbox, b->mailbox)))
                break;

            if (!(a->host && b->host && !strcmp(a->host, b->host)))
                break;

            a = a->next;
            b = b->next;
        }
        if ((a == NIL) && (b == NIL))
            d->reply_to = pool_strdup(d->pool, "");     /* Addresses match! */
        else
            d->reply_to =
                pool_strdup(d->pool, addr_text(pool, env->reply_to));

        d->reply_to
            =
            (char *) rfc1522_decode(pool_alloc(pool, strlen(d->reply_to)),
                                    strlen(d->reply_to), d->reply_to, NIL);
    } else
        d->reply_to = pool_strdup(d->pool, "");

    if (env->subject) {
        d->subject = pool_strdup(d->pool, env->subject);
        d->subject
            = (char *) rfc1522_decode(pool_alloc(pool, strlen(d->subject)),
                                      strlen(d->subject), d->subject, NIL);
    } else
        d->subject = pool_strdup(d->pool, "");

    /* Restore Fcc header and reply information */
    hdrs = ml_fetch_header(session, stream, msgno, NIL, fcc_hdrslist, &len, 0);
    if (hdrs) {
        hdrs = pool_strdup(d->pool, hdrs); /* Scratch copy */

        while ((hdr = get_single_hdr(&hdrs)) != NULL) {
            if (!(s = strchr(hdr, ':')))
                continue;

            *s++ = '\0';
            while ((*s == ' ') || (*s == '\t'))
                s++;

            if (!strcasecmp(hdr, "Fcc"))
                d->fcc = pool_strdup(d->pool, draft_parse_fcc(s));
            else if (!strcasecmp(hdr, "X-Reply-UID"))
                draft_parse_reply_uid(d, s);
            else if (!strcasecmp(hdr, "In-Reply-To"))
                d->in_reply_to = pool_strdup(d->pool, s);
            else if (!strcasecmp(hdr, "References"))
                d->references = pool_strdup(d->pool, s);
        }
    }

    /* Restore body */
    if ((body = ml_body(session, stream, msgno, "1")) == NIL)
        return (NIL);

    for (parameter = body->parameter; parameter; parameter = parameter->next) {
        if (strcasecmp(parameter->attribute, "charset") == 0) {
            charset = parameter->value;
            break;
        }
    }

    if (!(init_msg = ml_fetchbody(session, stream, msgno, "1", &len)))
        return (NIL);

    if (body->type == TYPETEXT) {
        /* Strip off encoding */
        switch (body->encoding) {
        case ENCBASE64:
            if (!
                (decode_msg =
                 (char *) rfc822_base64((unsigned char *) init_msg,
                                        body->size.bytes, &len))) {
                /* Decode failed */
                decode_msg = init_msg;
                len = body->size.bytes;
            }
            break;
        case ENCQUOTEDPRINTABLE:
            if (!
                (decode_msg =
                 (char *) rfc822_qprint((unsigned char *) init_msg,
                                        body->size.bytes, &len))) {
                /* Decode failed */
                decode_msg = init_msg;
                len = body->size.bytes;
            }
            break;
        case ENC7BIT:
        case ENC8BIT:
        case ENCBINARY:
        case ENCOTHER:
        default:
            decode_msg = init_msg;
            len = body->size.bytes;
        }

        d->body = utf8_from_string(d->pool, charset, decode_msg, len);

        if (decode_msg != init_msg)
            fs_give((void **) &decode_msg);

        /* Slightly unpleasant hack to remove final CRLF from message
           if it had two. ml_fetchbody() should always use CRLF */
        len = strlen(d->body);

        while ((len > 4) && !strcmp(d->body+len-4, "\015\012\015\012")) {
            d->body[len-2] = '\0';
            len -= 2;
        }
    } else
        d->body = pool_strdup(d->pool,
                              "(Message body was not text: suppressed)"
                              CRLF);

    /* Restore attachments */
    return (add_attachments(d, pool, stream, msgno));
}

/* ====================================================================== */

/* draft_write() *********************************************************
 *
 * Write current draft to postponed messages folder.
 *
 * Returns: T if sucessful. NIL => write failed.
 ***********************************************************************/

BOOL draft_write(struct draft * draft)
{
    struct session *session = draft->session;
    struct config  *config = session->config;
    struct request *request = session->request;
    struct pool *pool = request->pool;
    MAILSTREAM *stream;
    unsigned long msg_len;
    char *msg;
    STRING ms;

    if (session->draft_stream)
        stream = session->draft_stream;
    else
        stream = session->stream;

    if ((msg = draft_make_msg(draft, T, &msg_len)) == NIL)
        return (NIL);

    /* Convert simple "char *" string into c-client "STRING *" string */
    INIT(&ms, mail_string, msg, msg_len);

    /* Append message to end of postponed-msgs folder */
    if (!ml_append(session, stream,
                   session_mailbox(session, pool,
                                   session->draft_foldername), &ms))
        return (NIL);

    if (ml_have_error())
        return (NIL);

    /* Add draft folder to dircache if it doesn't already exist */
    if (!folderlist_lookup(folderlist_fetch(session),
                           session->draft_foldername)) {
        if (config->dualuse)
            folderlist_add(session->folderlist, session->draft_foldername,
                           NIL, NIL);
        else
            folderlist_add(session->folderlist, session->draft_foldername,
                           NIL, T);
    }

    return (T);
}

/* ====================================================================== */

/* draft_check() ********************************************************
 *
 * Check for postponed messages. Connects to IMAP server if necessary.
 * session: 
 *
 * Returns: T if postponed messages exist.
 ***********************************************************************/

BOOL draft_check(struct session * session)
{
    MAILSTATUS status;
    struct request *request = session->request;
    char *draft_mbox = session_mailbox(session, request->pool,
                                       session->draft_foldername);

    if (session->draft_stream && !session->draft_stream->halfopen) {
        /* Check that draft stream still valid (redundant in normal operation) */
        if (ml_ping(session, session->draft_stream)) {
            session->draft_last_ping_time = time(NIL);
            return (((session->draft_stream->nmsgs > 0) ? T : NIL));
        }
        ml_close(session, session->draft_stream);
        session->draft_stream = NIL;
    }

    if (!ml_status(session, session->stream, draft_mbox, SA_MESSAGES, &status))
        return(NIL);

    if (status.messages == 0)
        return(NIL);

    /* Attempt to open drafts folder */
    session->draft_stream
        = ml_open(session, session->draft_stream, draft_mbox, 0);

    if (!session->draft_stream)
        return (NIL);

    /* Folder opened successfully */
    session->draft_last_ping_time = time(NIL);

    if (session->draft_stream->nmsgs == 0)
        return (NIL);

    return (T);
}

/* draft_delete() *******************************************************
 *
 * Delete message from postponed folder. Returns: T on sucess.
 ***********************************************************************/

BOOL draft_delete(struct session * session, unsigned long msgno)
{
    struct request *request = session->request;
    char *name = session->draft_foldername;
    MAILSTREAM *stream;

    if ((session->draft_stream == NIL) && (draft_check(session) == NIL))
        return (NIL);

    stream = session->draft_stream;

    if (stream->nmsgs >= msgno) {
        if (!ml_flag(session, stream, string_itoa_tmp(msgno),
                     "\\DELETED", ST_SET))
            return (NIL);
    }

    ml_expunge(session, stream);

    /* Shut down draft stream if empty and not active */
    if ((session->stream != stream) && (stream->nmsgs == 0)) {
        ml_close(session, stream);
        session->draft_stream = NIL;

        ml_delete(session, session->stream,
                  session_mailbox(session, request->pool, name));

        folderlist_delete(session->folderlist, name);
    }

    return (T);
}

/* draft_is_reply() ******************************************************
 *
 * Is draft answer to a message?
 ************************************************************************/

BOOL draft_is_reply(struct draft * draft)
{
    return ((draft->reply_msguid && draft->reply_folder) ? T : NIL);
}

/* draft_flagged_answered() **********************************************
 *
 * Flag message as answered. However we have to find it first...
 ************************************************************************/

BOOL draft_flag_answered(struct draft * draft)
{
    struct session *session = draft->session;
    struct pool *pool = session->request->pool;
    MAILSTREAM *stream;
    unsigned long msgno = draft->reply_msgno;
    BOOL close = NIL;

    if (draft->reply_folder == NIL)
        return (NIL);

    if (!(stream = session_streams_find(session, draft->reply_folder))) {
        char *name = session_mailbox(session, pool, draft->reply_folder);

        if (!(stream = ml_open(session, NIL, name, 0))) {
            session_log(session,
                        ("[draft_flag_answered] Failed to flag message "
                         "as answered: Couldn't open %s"),
                        draft->reply_folder);
            return (NIL);
        }
        close = T;
    }

    if ((msgno == 0) || (msgno > stream->nmsgs) ||
        (ml_uid(session, stream, msgno) != draft->reply_msguid)) {
        if (!(msgno = ml_msgno(session, stream, draft->reply_msguid))) {
            if (close)
                ml_close(session, stream);

            session_log(session,
                        ("[draft_flag_answered] Failed to flag"
                         " message as answered:"
                         " Couldn't find message UID %lu in folder %s"),
                        draft->reply_msguid, draft->reply_folder);
            return (NIL);
        }
    }

    if (!ml_flag
        (session, stream, string_itoa_tmp(msgno), "\\ANSWERED", ST_SET)) {
        if (close)
            ml_close(session, stream);

        session_log(session,
                    ("[draft_flag_answered] Failed to flag message as "
                     "answered: %lu"), ml_errmsg());
        return (NIL);
    }

    if (close)
        ml_close(session, stream);

    return (T);
}
