/* $Cambridge: hermes/src/prayer/cmd/cmd_forward1.c,v 1.12 2012/04/30 14:54:09 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"

static void buffer_puts_mime(struct buffer *b, char *s)
{
    unsigned long len = strlen(s) + 20;
    char *buffer = pool_alloc(b->pool, len);    /* Decoded form smaller */
    char *d;

    d = (char *) rfc1522_decode((unsigned char *) buffer, len, s, NIL);

    bputs(b, d);
}

static char *string_decode_mime(struct pool *tpool, char *s)
{
    unsigned long len = strlen(s) + 20;
    char *buffer = pool_alloc(tpool, len);
    char *d;

    d = (char *) rfc1522_decode((unsigned char *) buffer, len, s, NIL);

    return (d);
}

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

static BOOL
add_attachments(struct session *session,
                struct draft *draft,
                struct pool *pool, MAILSTREAM * stream,
                unsigned long msgno, BOOL attach_first)
{
    BODY *body;
    PART *part;
    unsigned long section;
    char *hdr, *msg;
    unsigned long hdr_len, msg_len;
    char *name, *type, *encoding;
    PARAMETER *parameter;

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

    /* Deal with nested message as special case */
    if (body->type == TYPEMESSAGE) {
        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, "1", &hdr_len, FT_PEEK))
            && (msg = ml_fetchbody(session, stream, msgno, "1", &msg_len))) {
            draft_add_attachment(draft, "", type, encoding,
                                 hdr, hdr_len, msg, msg_len);
            return (T);
        }
        return (NIL);
    }

    /* Process top level multipart message */
    if (body->type != TYPEMULTIPART)
        return (T);

    if (body->subtype && !strcasecmp(body->subtype, "alternative"))
        return (T);

    if (!(part = body->nested.part))
        return (T);
    
    /* First part is typically text part displayed to user. Skip it unless
       we were unable to extract text part to display inline (add_text) */

    if (attach_first) {
        section = 1;
    } else {
        section = 2;
        part = part->next;
    }

    while (part) {
        char *stext = string_itoa(pool, section);

        body = &part->body;

        name = NIL;
        for (parameter = body->parameter; parameter;
             parameter = parameter->next) {
            if (!strcasecmp(parameter->attribute, "NAME"))
                name = parameter->value;
        }
        if (!name && body->description)
            name = body->description;

        if (!name)
            name = "";

        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))) {
            draft_add_attachment(draft, name, type, encoding, hdr, hdr_len,
                                 msg, msg_len);
        } else
            return (NIL);

        part = part->next; 
        section++;
    }
    return (T);
}

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

/* Small utility routine 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 void
add_text_single(struct buffer *b, char *s)
{
    if (!s) return;

    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++;
        }
    }
}


static BOOL
add_text(struct session *session,
         struct buffer *b,
         STRINGLIST * hdrslist,
         struct pool *pool, MAILSTREAM * stream, unsigned long msgno,
         BOOL *attach_first)
{
    struct options *options = session->options;
    struct prefs *prefs = options->prefs;
    unsigned long len;
    char *text;
    char *init_msg, *decode_msg, *type;
    BODY *body = NIL;
    char *section = "1";
    PARAMETER *parameter;
    char *charset = "ISO-8859-1";

    *attach_first = NIL;

    if (!(text = ml_fetch_header(session, stream, msgno,
                                 NIL, hdrslist, &len, 0)))
        return (NIL);

    buffer_puts_mime(b, text);

    /* Only include text of first part in body */
    if ((body = ml_body(session, stream, msgno, "1")) == NIL)
        return (NIL);

    if (body->type == TYPEMESSAGE)
        return (T);

    if (body->type == TYPEMULTIPART) {
        PART *part = body->nested.part;
        int   i = 1, body_plain = 0, body_html = 0, subsection;

        for (i = 1; part != NIL; part = part->next, i++) {
            if (!(body = &part->body))
                continue;

            if ((body->type != TYPETEXT) || !body->subtype)
                continue;

            if (!strcasecmp(body->subtype, "plain")) {
                if (!body_plain) body_plain = i;
            } else if (!strcasecmp(body->subtype, "html")) {
                if (!body_html) body_html = i;
            }
        }

        subsection = (body_plain) ? body_plain : body_html;

        if (!subsection) {
            bputs(b, "(Message body included as attachment)" CRLF);
            *attach_first = T;
            return(T);
        }
        section = pool_printf(pool, "1.%lu", subsection);
        if ((body = ml_body(session, stream, msgno, section)) == NIL)
            return (NIL);
    } else if (body->type != TYPETEXT) {
        bputs(b, "(Message body included as attachment)" CRLF);
        *attach_first = T;
        return (T);
    } else
        section = "1";

    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, section, &len)))
        return (NIL);

    /* 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;
    }
    type = pool_strcat3(pool, body_types[body->type], "/", body->subtype);
    string_lcase(type);

    if ((!strcasecmp(type, "text/html") && prefs->html_inline) ||
        (!strncasecmp(decode_msg, "<html>", strlen("<html>")) &&
         prefs->html_inline_auto)) {
        if (decode_msg == init_msg)
            decode_msg = strdup(init_msg);
        html_secure_strip_all(b,
                              utf8_from_string(pool, charset, decode_msg, len));
    } else {
        add_text_single(b, utf8_from_string(pool, charset, decode_msg, len));
    }
    if (decode_msg != init_msg)
        fs_give((void **) &decode_msg);

    return (T);
}

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

void cmd_forward1(struct session *session)
{
    struct config  *config  = session->config;
    struct request *request = session->request;
    struct draft *draft = session->draft;
    struct options *options = session->options;
    struct prefs *prefs = options->prefs;
    unsigned long offset;
    char *s;
    MAILSTREAM *stream = session->stream;
    struct buffer *b;
    MESSAGECACHE *elt;
    ENVELOPE *env;
    unsigned long count, count2, bytes;
    unsigned long att_count;
    struct role *role = NIL;
    char *postponed_text = "";
    struct msgmap *zm = session->zm;
    STRINGLIST *hdrslist;
    static STRINGLIST *myhdrslist = NIL;
    static char *short_hdrs[] =
        { "from", "to", "cc", "date", "subject", NIL };

    /* Set up hdrlist on first call only */
    if (!myhdrslist)
        myhdrslist = new_strlst(short_hdrs);

    hdrslist = (session->full_hdrs) ? NIL : myhdrslist;

    /* Clear session_message generated by cmd_forward() if we are redirected
     * here automatically */
    session_message_clear(session);

    request_decode_form(request);
    if ((s = assoc_lookup(request->form, "role")))
        role = role_find(options->role_list, s);

    /* Write out exiting draft */
    if (draft->have_draft) {
        if (!draft_write(draft)) {
            session_message_clear(session);
            session_alert(session,
                          "Failed to postpone existing draft before forward");
            session_log(session,
                        "[cmd_forward1] Failed to postponed draft message: %s",
                        ml_errmsg());
            session_redirect(session, session->request, "compose");
            return;
        }
        draft_free(draft);
        postponed_text = " Postponed existing draft.";
    }

    draft_role_set(draft, role);
    draft_init(draft);
    draft_init_rich_headers(draft);

    /* Create temporary scratch buffer */
    b = buffer_create(draft->pool, 0);

    /* Install existing body (includes signature) */
    if (draft->body && draft->body[0])
        bputs(b, draft->body);

    if (!session->aggregate) {
        /* Simple forward */
        BOOL attach_first = NIL;

        bprintf(b, CRLF "---------- Forwarded message ----------" CRLF);

        /* Add message text */
        if (!add_text(session, b, hdrslist, request->pool,
                      stream, session->current, &attach_first)) {
            session_redirect(session, request, "restart");
            return;
        }

        /* Add attachments if multipart message */
        if (!add_attachments(session, draft, request->pool,
                             stream, session->current, attach_first)) {
            session_redirect(session, request, "restart");
            return;
        }

        draft->body = buffer_fetch(b, 0, buffer_size(b), NIL);
        att_count = draft_att_count(draft);

        if (att_count > 1)
            session_message(session,
                            "Forwarding message %lu (%lu attachments).%s",
                            session->current, att_count, postponed_text);
        else if (att_count == 1)
            session_message(session,
                            "Forwarding message %lu (1 attachment).%s",
                            session->current, postponed_text);
        else
            session_message(session,
                            "Forwarding message %lu (no attachments).%s",
                            session->current, postponed_text);

        if (!
            (env =
             ml_fetch_structure(session, stream, session->current, NIL,
                                0))) {
            session_redirect(session, request, "restart");
            return;
        }

        if (env->subject && env->subject) {
            char *s = string_decode_mime(request->pool, env->subject);

            if (!strncasecmp(s, "Fwd: ", strlen("Fwd: ")))
                draft->subject = pool_strdup(draft->pool, s);
            else
                draft->subject = pool_strcat(draft->pool, "Fwd: ", s);
        }
        session_redirect(session, request, "compose");
        return;
    }

    /* Aggregate forward */

    /* Fetch overview for all messages in folder */
    if (!ml_fetch_overview(session, stream, "1:*", NIL)) {
        session_alert(session, "Failed to fetch folder overview: %s",
                      ml_errmsg());

        session_redirect(session, request, "list");
        return;
    }

    /* Work out number of messages involved, and approx size */
    count = 0L;
    bytes = 0L;
    for (offset = 1L; offset <= msgmap_size(zm); offset++) {
        unsigned long msgno = msgmap_value(zm, offset);

        if (prefs->use_mark_persist) {
            if (msgmap_has_mark(zm, msgno)) {
                count++;
                if ((elt = ml_elt(session, stream, msgno)))
                    bytes += elt->rfc822_size;
            }
        } else {
            if (msgmap_has_tmp_mark(zm, msgno)) {
                count++;
                if ((elt = ml_elt(session, stream, msgno)))
                    bytes += elt->rfc822_size;
            }
        }
    }

    /* Block godzillagrams created by "Mark All" and GByte sized mboxes */
    if ((config->draft_att_total_max > 0) &&
        (bytes > config->draft_att_total_max)) {
        session_message(session,
                        "Forwarded message (%lu MBytes) would be larger than"
                        " maximum allowed (%lu MBytes)",
                        bytes/(1024*1024),
                        config->draft_att_total_max/(1024*1024));

        session_redirect(session, request, "list");
        return;
    }

    if (count != 1)
        bputs(b, CRLF "---------- Forwarded messages ----------" CRLF);
    else
        bputs(b, CRLF "---------- Forwarded message ----------" CRLF);

    if (!msgmap_update(zm)) {
        session_redirect(session, request, "restart");
        return;
    }

    count2 = 0;
    for (offset = 1L; offset <= msgmap_size(zm); offset++) {
        unsigned long msgno = msgmap_value(zm, offset);
        BOOL forward_this;
        BOOL attach_first = NIL;

        if (prefs->use_mark_persist && msgmap_has_mark(zm, msgno))
            forward_this = T;
        else if (!prefs->use_mark_persist
                 && msgmap_has_tmp_mark(zm, msgno))
            forward_this = T;
        else
            forward_this = NIL;

        if (forward_this) {
            /* Add message text */

            if (count != 1)
                bprintf(b, CRLF "---------- Message %lu ----------" CRLF,
                        ++count2);

            if (!add_text
                (session, b, hdrslist, request->pool, stream, msgno,
                 &attach_first)) {
                session_redirect(session, request, "restart");
                return;
            }

            if (count != 1)
                bputs(b, "" CRLF);

            /* Add attachments if multipart message */
            if (!add_attachments
                (session, draft, request->pool, stream, msgno, attach_first)) {
                session_redirect(session, request, "restart");
                return;
            }
        }
    }
    draft->body = buffer_fetch(b, 0, buffer_size(b), NIL);
    att_count = draft_att_count(draft);

    if (count != 1) {
        draft->subject
            =
            pool_printf(draft->pool, "Fwd: %lu separate messages", count);

        if (att_count > 1)
            session_message(session,
                            "Forwarding %lu messages (%lu attachments).%s",
                            count, att_count, postponed_text);
        else if (att_count == 1)
            session_message(session,
                            "Forwarding %lu messages (1 attachment).%s",
                            count, postponed_text);
        else
            session_message(session,
                            "Forwarding %lu messages (no attachments).%s",
                            count, postponed_text);
    } else {
        draft->subject = pool_strdup(draft->pool, "Fwd: Single message");

        if (att_count > 1)
            session_message(session,
                            "Forwarding single message (%lu attachments).%s",
                            att_count, postponed_text);
        else if (att_count == 1)
            session_message(session,
                            "Forwarding single message (1 attachment).%s",
                            postponed_text);
        else
            session_message(session,
                            "Forwarding single message (no attachments).%s",
                            postponed_text);
    }

    if (prefs->use_agg_unmark) {
        msgmap_unmark_all(session->zm);
        msgmap_disable_zoom(session->zm);
    }

    session_redirect(session, request, "compose");
}
