/* $Cambridge: hermes/src/prayer/cmd/cmd_display.c,v 1.5 2008/06/05 12:03:31 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 BOOL simple_string_compare(char *s1, char *s2)
{
    if ((s1 == NIL) && (s2 == NIL))
        return T;

    if ((s1 == NIL) || (s2 == NIL))
        return NIL;

    return (!strcmp(s1, s2));
}

/* Compare two ADDRESS structures, see if identical
 *   Returns: T   => definitely identical, can use shortcuts.
 *            NIL => Not clear whether identical. Don't try to use shortcuts.
 */

static BOOL simple_address_compare(ADDRESS * addr1, ADDRESS * addr2)
{
    /* Get rid of two difficult cases quickly */

    if ((addr1 == NIL) || (addr2 == NIL))
        return NIL;

    if ((addr1->next != NIL) || (addr2->next != NIL))
        return NIL;

    if (simple_string_compare(addr1->personal, addr2->personal) &&
        simple_string_compare(addr1->adl, addr2->adl) &&
        simple_string_compare(addr1->mailbox, addr2->mailbox) &&
        simple_string_compare(addr1->host, addr2->host) &&
        simple_string_compare(addr1->error, addr2->error))
        return T;

    return NIL;
}

static BOOL
cmd_display_addr(struct template_vals *tvals, char *array, ADDRESS * addr,
                 struct abook *abook)
{
    struct pool *pool = tvals->pool;
    ADDRESS *a;
    char *email;
    struct abook_entry *abe;
    unsigned long offset = 0;

    for (a = addr; a; a = a->next) {
        template_vals_foreach_init(tvals, array, offset);
        if (a->next)
            template_vals_foreach_string(tvals, array, offset, "next", "1");

        if (!(a->mailbox && a->host)) {
            /* Something that we can't parse sensibly */
            template_vals_foreach_string(tvals, array, offset, "raw",
                                         addr_text(pool, a));
            offset++;
            continue;
        }

        email = pool_strcat3(pool, a->mailbox, "@", a->host);
        template_vals_foreach_string(tvals, array, offset, "email", email);

        abe = abook_find_email(abook, email);
        if (abe && abe->alias) {
            template_vals_foreach_string(tvals, array, offset,
                                         "alias", abe->alias);
        }

        if (a->personal && a->personal[0]) {
            unsigned long len = strlen(a->personal) + 20;
            char *tmp = pool_alloc(pool, len);   /* Decoded form smaller */
            char *d = (char *) rfc1522_decode((unsigned char *) tmp,
                                              len, a->personal, NIL);

            template_vals_foreach_string(tvals, array, offset, "personal", d);
        }
        offset++;
    }
    return(T);
}

static BOOL
cmd_display_addhdrs(struct session *session,
                   MAILSTREAM *stream, unsigned long msgno)
{
    struct template_vals *tvals = session->template_vals;
    struct abook *abook = session->options->abook;
    MESSAGECACHE *elt;
    ENVELOPE *env;

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

    if (session->full_hdrs)
        template_vals_ulong(tvals, "$full_hdrs", 1);

    if (env->reply_to
        && !simple_address_compare(env->reply_to, env->from))
        cmd_display_addr(tvals, "@reply_to", env->reply_to, abook);

    cmd_display_addr(tvals, "@from", env->from, abook);
    if (env->to)
        cmd_display_addr(tvals, "@to", env->to, abook);
    if (env->cc)
        cmd_display_addr(tvals, "@cc", env->cc, abook);

    if (env->date)
        template_vals_string(tvals, "$date", (char *)env->date);
    else
        template_vals_string(tvals, "$date", "(Unknown date)");

    if (env->subject)
        template_vals_string(tvals, "$subject", env->subject);
    else
        template_vals_string(tvals, "$subject", "(No subject)");

    return(T);
}

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

static BOOL
cmd_display_addnav(struct session *session,
                   MAILSTREAM *stream, unsigned long msgno)
{
    struct template_vals *tvals = session->template_vals;
    struct msgmap *zm = session->zm;
    unsigned long zm_offset = msgmap_find(zm, msgno);
    unsigned long uid;
    MESSAGECACHE *elt;

    if (!(elt = ml_elt(session, stream, msgno)))
        return (NIL);
    if (elt->deleted)
        template_vals_hash_ulong(tvals, "$nav", "deleted", 1);

    uid = ml_uid(session, stream, msgno);
    template_vals_hash_ulong(tvals, "$nav", "cur_msg", msgno);
    template_vals_hash_ulong(tvals, "$nav", "cur_uid", uid);
    template_vals_hash_ulong(tvals, "$nav", "msg_count",msgmap_size(zm)); 

    if (msgmap_has_mark(zm, msgno))
        template_vals_hash_ulong(tvals, "$nav", "marked", 1);

    if (zm_offset > 1) {
        msgno = msgmap_value(zm, zm_offset - 1);
        uid = ml_uid(session, stream, msgno);

        template_vals_hash_ulong(tvals, "$nav", "prev_msg", msgno);
        template_vals_hash_ulong(tvals, "$nav", "prev_uid", uid);
    }
    if (zm_offset < msgmap_size(zm)) {
        msgno = msgmap_value(zm, zm_offset + 1);
        uid = ml_uid(session, stream, msgno);

        template_vals_hash_ulong(tvals, "$nav", "next_msg", msgno);
        template_vals_hash_ulong(tvals, "$nav", "next_uid", uid);
    }
    return(T);
}

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

/* show_structure():
 *   Recursively render message MIME structure as collection of
 *   nested <ol> with links to body parts.
 *
 *  session:
 *    tvals: Template vals to render into
 *  offsetp: Offset into @atts list
 *    msgno: Message that we are rendering
 *   msguid: Message UID
 *     body: Current body part that we are rendering
 *  section: IMAP session number prefix for this body part
 *        i: Offset count for multipart messages. ($section$i gives offset).
 *             (section and i could be combined, this approach requires
 *              fewer temporary copies of strings).
 */

static void
show_structure_simple(struct template_vals *tvals,
                      unsigned long offset,
                      BODY * body)
{
    struct pool *pool = tvals->pool;
    PARAMETER *parameter;
    char *type, *name, *size;

    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 = "";
    template_vals_foreach_string(tvals, "@atts", offset, "name", name);
    type = pool_strcat3(pool, body_types[body->type], "/", body->subtype);
    string_lcase(type);
    template_vals_foreach_string(tvals, "@atts", offset, "type", type);
    template_vals_foreach_ulong(tvals, "@atts", offset,
                                "lines", body->size.lines);

    if (body->size.bytes >= 2048)
        size = pool_printf(pool, "%lu KBytes", body->size.bytes / 1024);
    else if (body->size.bytes != 1)
        size = pool_printf(pool, "%lu bytes", body->size.bytes);
    else
        size = "1 byte";
    template_vals_foreach_string(tvals, "@atts", offset, "size", size);
}

static void
show_structure(struct template_vals *tvals, unsigned long *offsetp,
               BODY * body, char *parent, long i)
{
    struct pool *pool = tvals->pool;
    PART *part;
    char *section;

    if (parent) {
        template_vals_foreach_init(tvals, "@atts", *offsetp);
        template_vals_foreach_ulong(tvals, "@atts", *offsetp, "start_item", 1);
        (*offsetp)++; 
    }

    template_vals_foreach_init(tvals, "@atts", *offsetp);
    if (parent && parent[0])
        section = pool_printf(pool, "%s.%d", parent, i);
    else
        section = pool_printf(pool, "%d", i);
    template_vals_foreach_string(tvals, "@atts", *offsetp, "section", section);

    switch (body->type) {
    case TYPEMULTIPART:
        template_vals_foreach_ulong(tvals, "@atts", *offsetp, "start_list", 1);
        if (parent)
            template_vals_foreach_ulong(tvals, "@atts", *offsetp,
                                        "nested_multipart", 1);
        (*offsetp)++;
        for (i = 1, part = body->nested.part; part != NIL;
             part = part->next, i++) {
            show_structure(tvals, offsetp, &part->body,
                           (parent) ? section : "", i);
        }
        template_vals_foreach_init(tvals, "@atts", *offsetp);
        template_vals_foreach_ulong(tvals, "@atts", *offsetp, "end_list", 1);
        if (parent)
            template_vals_foreach_ulong(tvals, "@atts", *offsetp,
                                        "nested_multipart", 1);
        (*offsetp)++; 
        break;
    case TYPEMESSAGE:
        template_vals_foreach_ulong(tvals, "@atts", *offsetp, "is_msg", 1);
        show_structure_simple(tvals, *offsetp, body);
        (*offsetp)++;

        if (!strcmp(body->subtype, "RFC822")
            && (body = body->nested.msg->body)) {
            template_vals_foreach_init(tvals, "@atts", *offsetp);
            template_vals_foreach_ulong(tvals, "@atts", *offsetp,
                                        "start_list", 1);
            (*offsetp)++;
            if (body->type == TYPEMULTIPART) {
                /* Nested multipart message */
                for (i = 1, part = body->nested.part; part != NIL;
                     part = part->next, i++) {
                    show_structure(tvals, offsetp, &part->body, section, i);
                }
            } else {
                /* Nested singlepart message */
                show_structure(tvals, offsetp, body, section, 1);
            }
            template_vals_foreach_init(tvals, "@atts", *offsetp);
            template_vals_foreach_ulong(tvals, "@atts", *offsetp,
                                        "end_list", 1);
            (*offsetp)++;
        }
        break;
    case TYPETEXT:
        template_vals_foreach_ulong(tvals, "@atts", *offsetp, "is_text", 1);
        /* Fall through to default */
    default:
        show_structure_simple(tvals, *offsetp, body);
        (*offsetp)++;
        break;
    }

    if (parent) {
        template_vals_foreach_init(tvals, "@atts", *offsetp);
        template_vals_foreach_ulong(tvals, "@atts", *offsetp, "end_item", 1);
        (*offsetp)++; 
    }
}

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

/* show_textpart():
 *
 * Render given text section into output buffer. Code factored out from
 * cmd_display() so that it can be used for simple text/plain messages,
 * as well as multipart. Could also be used to display all text bodyparts.
 *
 */

static BOOL
show_textpart(struct session *session, MAILSTREAM *stream,
              unsigned long msgno, char *section)
{
    struct options *options = session->options;
    struct prefs *prefs = options->prefs;
    struct request *request = session->request;
    struct pool *pool = request->pool;
    struct buffer *b = request->write_buffer;
    char *init_msg, *decode_msg;
    char *charset = "ISO-8859-1";
    unsigned long len;
    BODY *body = NIL;
    BOOL show_html = NIL;
    PARAMETER *parameter;

    if (!(body = ml_body(session, stream, msgno, section)))
        return(NIL);

    if (body->type != TYPETEXT) {
        session_message(session, "Invalid body type (should never happen)");
        session_log(session, "show_textpart(): Invalid bodypart");
        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, section, &len)))
        return(NIL);

    /* Strip off encoding */
    switch (body->encoding) {
    case ENCBASE64:
        decode_msg = (char *) rfc822_base64((unsigned char *) init_msg,
                                            body->size.bytes, &len);

        if (!decode_msg) {
            /* Decode failed */
            decode_msg = init_msg;
            len = body->size.bytes;
        }
        break;
    case ENCQUOTEDPRINTABLE:
        decode_msg = (char *) rfc822_qprint((unsigned char *) init_msg,
                                            body->size.bytes, &len);

        if (!decode_msg) {
            /* 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;
    }

    if ((prefs->html_inline) &&
        body->subtype && !strcasecmp(body->subtype, "html"))
        show_html = T;
    else if (prefs->html_inline_auto) {
        char *s = decode_msg;

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

        if (!strncasecmp(s, "<html>", strlen("<html>")))
            show_html = T;
        else if (!strncasecmp
                 (s, "<!doctype html ", strlen("<!doctype html ")))
            show_html = T;
        else
            show_html = NIL;
    } else
        show_html = NIL;

    if (show_html) {
        if (decode_msg == init_msg)
            decode_msg = strdup(init_msg);
        html_secure(session, b,
                    utf8_from_string(pool, charset, decode_msg, len));
    } else {
        bprintf(b, "<pre>" CRLF);
        wrap_line_html(session, b,
                       utf8_from_string(pool, charset, decode_msg, len), 80);
        bprintf(b, "</pre>" CRLF);
    }

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

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

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

void cmd_display(struct session *session)
{
    struct template_vals *tvals = session->template_vals;
    struct options *options = session->options;
    struct prefs *prefs = options->prefs;
    struct request *request = session->request;
    struct msgmap *zm = session->zm;
    struct pool *pool = request->pool;
    unsigned long msgno, msguid;
    char *section;
    char *init_msg, *decode_msg;
    unsigned long len;
    struct buffer *b = request->write_buffer;
    MAILSTREAM *stream = session->stream;
    BODY *body = NIL;
    unsigned long offset = 0;

    if (request->method == POST) {
        char *cmd, *folder;

        request_decode_form(request);

        if (assoc_lookup(request->form, "sub_folder_dialogue") &&
            (folder = assoc_lookup(request->form, "folder")) && folder[0])
        {
            if ((cmd = assoc_lookup(request->form, "command")) &&
                !strcmp(cmd, "copy")) {
                session->aggregate = T;
                session_redirect(session, request,
                                 pool_strcat(request->pool, "copy_msg/",
                                             folder));
            } else
                session_redirect(session, request,
                                 pool_strcat(request->pool, "change/",
                                             folder));
            return;
        }
    }

    if (!msgmap_update(zm)) {
        session_message(session, "Failed to update msgmap");
        session_redirect(session, request, "restart");
        return;
    }

    if (msgmap_size(zm) == 0) {
        session_message(session, "Folder is empty");
        session_redirect(session, request, "list");
        return;
    }

    /* Save options if anything changed */
    if (options->save)
        session_streams_save_options(session);

    /* Record last command for compose/cancel and friends */
    if (!session->draft->have_draft)
        session->compose_parent_cmd = "display";

    /* Record last command for save/cancel */
    session->copy_parent_cmd = "display";

    /* Record last command for addressbook take */
    session->take_parent_cmd = "display";

    section = NIL;
    if (request->argc >= 4)
        section = request->argv[3];

    if (request->argc >= 3) {
        msgno = atoi(request->argv[1]);
        msguid = atoi(request->argv[2]);

        if (!(msgno = stream_check_uid(session, stream, msgno, msguid))) {
            session_redirect(session, request, "list");
            return;
        }
    } else {
        msgno = session->last_displayed;

        if ((msgno == 0) || (msgno > msgmap_size(zm)))
            msgno = msgmap_value(zm, msgmap_size(zm));

        msguid = ml_uid(session, stream, msgno);
    }

    session->current = msgno;
    session->last_displayed = msgno;

    if (session->stream == session->draft_stream)
        template_vals_ulong(tvals, "$is_postponed_folder", 1);

    if (prefs->use_mark_persist)
        template_vals_ulong(tvals, "$use_persist", 1);

    if (prefs->use_tail_banner)
        template_vals_ulong(tvals, "$use_tail_banner", 1);

    cmd_display_addnav(session, stream, msgno);

    if (session->full_hdrs) {
        char *hdr;
        unsigned long len;

        template_vals_ulong(tvals, "$full_hdrs", 1);
        session_seed_template(session, tvals);
        template_expand("display", tvals, b);
        bprintf(b, "<pre>" CRLF);
        if ((hdr = ml_fetch_header(session, stream, msgno, NIL, NIL, &len, 0)))
            wrap_text_html_quotes(b, hdr, 80, T);
        bprintf(b, "</pre>" CRLF);
    } else {
        cmd_display_addhdrs(session, stream, msgno);
        session_seed_template(session, tvals);
        template_expand("display", tvals, b);
    }

    /* Fetch message structure */
    if (!ml_fetch_structure(session, stream, msgno, &body, NIL)) {
        session_redirect(session, request, "restart");
        return;
    }

    /* Print out MIME bodystructure if multipart message */
    if ((body->type == TYPEMULTIPART) || (body->type == TYPEMESSAGE)) {
        offset = 0;
        show_structure(tvals, &offset, body, NIL, 1);
        template_expand("display_mime", tvals, b);
    }

    if (section)
        bprintf(b, "<p>Section %s:</p>"CRLF, section);
    else
        section = "1";

    /* Get body structure for specific message part that we are looking at */
    if ((body = ml_body(session, stream, msgno, section)) == NIL) {
        session_redirect(session, request, "restart");
        return;
    }

    if (body->type == TYPEMESSAGE) {
        char *text;
        static char *short_hdrs[] =
            { "from", "to", "cc", "date", "subject", NIL };
        static STRINGLIST *hdrslist_cache = NIL;
        STRINGLIST *hdrslist;

        if (!hdrslist_cache)
            hdrslist_cache = new_strlst(short_hdrs);

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

        bprintf(b, "<pre>" CRLF);
        if (!
            ((text =
              ml_fetch_header(session, stream, msgno, section, hdrslist,
                              &len, 0))
             && (init_msg =
                 ml_fetch_body(session, stream, msgno, section, &len,
                               0)))) {
            session_redirect(session, request, "restart");
            return;
        }
        html_quote_string(b, text);

        /* Don't try to decode the message contents: substructure links do this */
        decode_msg = init_msg;

        html_quote_string(b, decode_msg);
        bprintf(b, "</pre>" CRLF);

    } else 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) {
            if (section)
                section = pool_printf(pool, "%s.%lu", section, subsection);
            else
                section = pool_printf(pool, "%lu", subsection);

            if (!show_textpart(session, stream, msgno, section)) {
                session_redirect(session, request, "restart");
                return;
            }
        }
    } else if (body->type == TYPETEXT) {
        if (!show_textpart(session, stream, msgno, section)) {
            session_redirect(session, request, "restart");
            return;
        }
    } else {
        bputs(b, "<pre>" CRLF);
        bputs(b, "Cannot display this object. Download link follows:" CRLF);
        bputs(b, "</pre>" CRLF);

        offset = 0;
        show_structure(tvals, &offset, body, NIL, 1L);
        template_expand("display_mime", tvals, b);
    }

    template_expand("display_tail", tvals, b);
    /* Send out the response */
    response_html(request, 200);
}
