/* $Cambridge: hermes/src/prayer/session/display.c,v 1.16 2010/07/12 10:34:51 dpc22 Exp $ */

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

BOOL
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))
        display_addr(tvals, "@reply_to", env->reply_to, abook);

    display_addr(tvals, "@from", env->from, abook);
    if (env->to)
        display_addr(tvals, "@to", env->to, abook);
    if (env->cc)
        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) {
        char *subject = env->subject;

        subject = (char *)
            rfc1522_decode(pool_alloc(tvals->pool, strlen(subject)),
                           strlen(subject), subject, NIL);

        template_vals_string(tvals, "$subject", subject);
    } else
        template_vals_string(tvals, "$subject", "(No subject)");

    return(T);
}

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

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


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

/* Parse RFC 2231 name of form ISO-8859-1''%a3
 *
 * Returns simple UTF-8 string or NIL if the input was NIL.
 */

static char *parse_2231(char *value, struct pool *pool)
{
    char *s, *t, *charset;

    if (!value)
        return(NIL);

    value = pool_strdup(pool, value);

    if ((s=strchr(value, '\'')) && ((t=strchr(s+1, '\'')))) {
        *s++ = '\0';
        *t++ = '\0';

        charset = value;
        value   = t;

        string_url_decode(value);

        if (!strcasecmp(charset, "utf-8"))
            return(value);

        if (charset[0] == '\0')
            charset = "ISO-8859-1";   /* Default not specified by RFC 2231 */

        return(utf8_from_string(pool, charset, value, strlen(value)));
    }
    return(NIL);
}

static char *body_get_name(BODY *body, struct pool *pool)
{
    PARAMETER *parameter;
    char *name = NIL;

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

    if (!name && body->disposition.type) {
        for (parameter = body->disposition.parameter; parameter;
             parameter = parameter->next) {
            if (!strcasecmp(parameter->attribute, "FILENAME*")) {
                name = parse_2231(parameter->value, pool);
            } else if (!strcasecmp(parameter->attribute, "FILENAME")) {
                name = parameter->value;
            }
        }
    }
    if (!name)
        name = "";

    return(name);
}


/* 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;
    char *type, *name, *size;

    name = body_get_name(body, pool);
    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,
              BOOL html_show_images,
              int depth)
{
    struct template_vals *tvals = session->template_vals;
    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);

    /* Include section numbers for tree leaves only */
    if (section && (depth > 0))
        bprintf(b, "<p>Part %s:</p>"CRLF, section);

    if (body->type != TYPETEXT) {
        session_alert(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 (!prefs->html_remote_images) {
            template_vals_ulong(tvals, "$is_html", 1);
            if (html_show_images)
                template_vals_ulong(tvals, "$html_images_shown", 1);
            template_expand("display_images", tvals, b);
        }

        if (decode_msg == init_msg)
            decode_msg = strdup(init_msg);

        bputs(b, "<div class=\"fix_fonts\">"CRLF);
        html_secure(session, b, html_show_images,
                    utf8_from_string(pool, charset, decode_msg, len));
        bputs(b, "</div>"CRLF);
    } 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);
}

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

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 BOOL
show_message_hdrs(struct session *session, MAILSTREAM *stream,
                  unsigned long msgno, char *section, int depth)
{
    static char *short_hdrs[] =
        { "from", "to", "cc", "date", "subject", NIL };
    static STRINGLIST *hdrslist_cache = NIL;
    STRINGLIST *hdrslist;
    struct request *request = session->request;
    struct buffer *b = request->write_buffer;
    char *hdr;
    unsigned long len;

    if (!hdrslist_cache)
        hdrslist_cache = new_strlst(short_hdrs);
    hdrslist = (session->full_hdrs) ? NIL : hdrslist_cache;

    if (section && (depth > 0))
        bprintf(b, "<p>Part %s:</p>"CRLF, section);

    bprintf(b, "<pre>"CRLF);

    hdr = ml_fetch_header(session, stream, msgno, section, hdrslist, &len, 0);
    if (!hdr)
        return(NIL);
    html_quote_string(b, hdr);

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

    return(T);

}

static BOOL
show_message(struct session *session, MAILSTREAM *stream,
             unsigned long msgno, char *section, int depth)
{
    struct request *request = session->request;
    struct buffer *b = request->write_buffer;
    char *msg;
    unsigned long len;

    if (!show_message_hdrs(session, stream, msgno, section, depth))
        return(NIL);

    bprintf(b, "<pre>"CRLF);

    msg =  ml_fetch_body(session, stream, msgno, section, &len, 0);
    if (!msg)
        return(NIL);
    html_quote_string(b, msg);  /* No attempt to decode: raw text */

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

    return(T);
}

static BOOL
show_attachment(struct session *session, MAILSTREAM *stream,
                unsigned long msgno, char *section, int depth)
{
    struct request *request = session->request;
    struct buffer *b = request->write_buffer;
    struct pool *pool = request->pool;
    BODY *body = NIL;
    char *type, *name, *size;
    unsigned long uid = ml_uid(session, stream, msgno);

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

    name = body_get_name(body, pool);
    type = pool_strcat3(pool, body_types[body->type], "/", body->subtype);
    string_lcase(type);

    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";

    bprintf(b, "<p>Part %s: ", section);

    /* XXX Evil session URLs. */
    bprintf(b, "<a href=\"%s/NOSEQ/rawdisplay/%lu/%lu/%s/%s/%s\">",
            session->url_prefix_bsession, msgno, uid, section,
            string_url_encode(pool, type),
            string_url_encode(pool, name));
    html_quote_string(b, name);
    bputs(b, " ");
    html_quote_string(b, type);
    bprintf(b, " (%s)</a></p>"CRLF, size);

    return(T);
}

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

static int
find_single_text_section(BODY *body)
{
    PART *part = body->nested.part;
    int   i = 1, body_plain = 0, body_html = 0;

    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;
        }
    }
    return((body_plain) ? body_plain : body_html);
}

static BOOL
show_tree(struct session *session, MAILSTREAM *stream, unsigned long msgno,
          BODY *body, char *parent, long i, BOOL html_show_images, int depth)

{
    struct request *request = session->request;
    struct pool *pool = request->pool;
    PART *part;
    char *section;

    if (parent && parent[0]) {
        section = pool_printf(pool, "%s.%d", parent, i);
    } else 
        section = pool_printf(pool, "%d", i);

    switch (body->type) {
    case TYPETEXT:
        if (!show_textpart(session, stream, msgno, section,
                           html_show_images, depth))
            return(NIL);
        break;
    case TYPEMULTIPART:
        if (body->subtype && !strcasecmp(body->subtype, "alternative")) {
            int subsection = find_single_text_section(body);
            
            if (subsection) {
                if (parent)
                    section = pool_printf(pool, "%s.%lu", section, subsection);
                else
                    section = pool_printf(pool, "%lu", subsection);
        
                if (!show_textpart(session, stream, msgno, section,
                                   html_show_images, depth))
                    return(NIL);
            }
        } else {
            for (i = 1, part = body->nested.part; part != NIL;
                 part = part->next, i++) {

                if (!show_tree(session, stream, msgno, &part->body,
                               (parent) ? section : "", i,
                               html_show_images, depth+1))
                    return(NIL);
            }
        }
        break;
    case TYPEMESSAGE:
        if (!show_message_hdrs(session, stream, msgno, section, depth))
            return(NIL);

        if (!strcmp(body->subtype, "RFC822")
            && (body = body->nested.msg->body)) {
            if (body->type == TYPEMULTIPART) {
                /* Nested multipart message */
                for (i = 1, part = body->nested.part; part != NIL;
                     part = part->next, i++) {
                    if (!show_tree(session, stream, msgno,
                                   &part->body, section, i,
                                   html_show_images, depth+1))
                        return(NIL);
                }
            } else {
                /* Nested singlepart message */
                if (!show_tree(session, stream, msgno,
                               body, section, 1, html_show_images, depth+1))
                    return(NIL);
            }
        }
        break;
    default: 
        if (!show_attachment(session, stream, msgno, section, depth))
            return(NIL);
        break;
    }
    return(T);
}

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

BOOL
display_body(struct session *session,
             struct request *request,
             MAILSTREAM *stream,
             unsigned long msgno,
             char *section,
             char *cmd,
             BOOL html_show_images)
{
    struct template_vals *tvals = session->template_vals;
    struct buffer *b = request->write_buffer;
    BODY *body = NIL;

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

    /* Print out MIME bodystructure if multipart message */
    if ((body->type == TYPEMULTIPART) || (body->type == TYPEMESSAGE)) {
        unsigned long offset = 0;

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

    if (section && section[0]) {
        bprintf(b, "<p>Part %s:</p>"CRLF, section);

        template_vals_string(tvals, "$section", section);

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

        switch (body->type) {
        case TYPETEXT:
            show_textpart(session, stream, msgno, section, html_show_images, 0);
            break;
        case TYPEMULTIPART:
            show_tree(session, stream, msgno,
                      body, section, 1, html_show_images, 0);
            break;
        case TYPEMESSAGE:
            show_message(session, stream, msgno, section, 0);
            break;
        }
    } else {
        show_tree(session, stream, msgno, body, NIL, 1, html_show_images, 0);
    }
    
    return(T);
}
