From 57b6d7ccfa222cc6f2991b1132adb707f67234bf Mon Sep 17 00:00:00 2001 From: Yubin Ruan Date: Sun, 1 Apr 2018 21:20:01 +0800 Subject: [PATCH] Enable editing of multipart/multilingual mails --- compose.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++ functions.h | 2 + handler.c | 8 ++-- mutt/mime.c | 51 +++++++++++++++++++++++ mutt/mime.h | 5 +++ opcodes.h | 2 + sendlib.c | 3 ++ 7 files changed, 182 insertions(+), 4 deletions(-) diff --git a/compose.c b/compose.c index 0ff704c66..0acf00439 100644 --- a/compose.c +++ b/compose.c @@ -1171,6 +1171,104 @@ int mutt_compose_menu(struct Header *msg, char *fcc, size_t fcclen, menu->redraw = REDRAW_INDEX; break; + case OP_COMPOSE_GROUP_LINGUAL: + { + if (menu->tagged < 2) + { + mutt_error( + _("Grouping multilingual requires at least 2 tagged messages.")); + break; + } + + /* traverse to see whether all the parts have Content-Language: set */ + struct Body *b = msg->content; + int tagged_with_lang_num = 0; + for (i = 0; b; b = b->next) + if (b->tagged && b->language && *b->language) + tagged_with_lang_num++; + + if (menu->tagged != tagged_with_lang_num) + { + if (mutt_yesorno( + _("Not all parts have Content-Language: set, continue?"), MUTT_YES) != MUTT_YES) + { + mutt_message(_("Not sending this message.")); + break; + } + } + + struct Body *group = mutt_body_new(); + group->type = TYPEMULTIPART; + group->subtype = mutt_str_strdup("multilingual"); + group->disposition = DISPINLINE; + + struct Body *alts = NULL; + /* group tagged message into a multipart/multilingual */ + struct Body *bptr = msg->content; + for (i = 0; bptr;) + { + if (bptr->tagged) + { + bptr->tagged = false; + bptr->disposition = DISPINLINE; + + /* for first match, set group desc according to match */ +#define LINGUAL_TAG "Multilingual part for \"%s\"" + if (!group->description) + { + char *p = bptr->description ? bptr->description : bptr->filename; + if (p) + { + group->description = + mutt_mem_calloc(1, strlen(p) + strlen(LINGUAL_TAG) + 1); + sprintf(group->description, LINGUAL_TAG, p); + } + } + + /* append bptr to the alts list, + * and remove from the msg->content list */ + if (alts == NULL) + { + group->parts = alts = bptr; + bptr = bptr->next; + alts->next = NULL; + } + else + { + alts->next = bptr; + bptr = bptr->next; + alts = alts->next; + alts->next = NULL; + } + + for (int j = i; j < actx->idxlen - 1; j++) + { + actx->idx[j] = actx->idx[j + 1]; + actx->idx[j + 1] = NULL; /* for debug reason */ + } + actx->idxlen--; + } + else + { + bptr = bptr->next; + i++; + } + } + + group->next = NULL; + mutt_generate_boundary(&group->parameter); + + /* if no group desc yet, make one up */ + if (!group->description) + group->description = mutt_str_strdup("unknown multilingual group"); + + struct AttachPtr *gptr = mutt_mem_calloc(1, sizeof(struct AttachPtr)); + gptr->content = group; + update_idx(menu, actx, gptr); + } + menu->redraw = REDRAW_INDEX; + break; + case OP_COMPOSE_ATTACH_FILE: { char *prompt = _("Attach file"); @@ -1423,6 +1521,23 @@ int mutt_compose_menu(struct Header *msg, char *fcc, size_t fcclen, mutt_message_hook(NULL, msg, MUTT_SEND2HOOK); break; + case OP_COMPOSE_EDIT_LANGUAGE: + CHECK_COUNT; + buf[0] = 0; /* clear buffer first */ + if (CURATTACH->content->language) + mutt_str_strfcpy(buf, CURATTACH->content->language, sizeof(buf)); + if ((mutt_get_field("Content-Language: ", buf, sizeof(buf), 0) == 0) && + mutt_mime_valid_language(buf)) + { + CURATTACH->content->language = mutt_str_strdup(buf); + menu->redraw = REDRAW_CURRENT | REDRAW_STATUS; + mutt_clear_error(); + } + else + mutt_error(_("Invalid language, default to none.")); + mutt_message_hook(NULL, msg, MUTT_SEND2HOOK); + break; + case OP_COMPOSE_EDIT_ENCODING: CHECK_COUNT; mutt_str_strfcpy(buf, ENCODING(CURATTACH->content->encoding), sizeof(buf)); diff --git a/functions.h b/functions.h index 17e590000..d3ab8c292 100644 --- a/functions.h +++ b/functions.h @@ -436,6 +436,7 @@ const struct Binding OpCompose[] = { /* map: compose */ #endif { "edit-from", OP_COMPOSE_EDIT_FROM, "\033f" }, { "edit-headers", OP_COMPOSE_EDIT_HEADERS, "E" }, + { "edit-language", OP_COMPOSE_EDIT_LANGUAGE, "\014" }, { "edit-message", OP_COMPOSE_EDIT_MESSAGE, "e" }, { "edit-mime", OP_COMPOSE_EDIT_MIME, "m" }, #ifdef USE_NNTP @@ -452,6 +453,7 @@ const struct Binding OpCompose[] = { /* map: compose */ { "forget-passphrase", OP_FORGET_PASSPHRASE, "\006" }, { "get-attachment", OP_COMPOSE_GET_ATTACHMENT, "G" }, { "group-alternatives", OP_COMPOSE_GROUP_ALTS, "&" }, + { "group-multilingual", OP_COMPOSE_GROUP_LINGUAL, "^" }, { "ispell", OP_COMPOSE_ISPELL, "i" }, #ifdef MIXMASTER { "mix", OP_COMPOSE_MIX, "M" }, diff --git a/handler.c b/handler.c index 60c55e706..077a96202 100644 --- a/handler.c +++ b/handler.c @@ -1189,7 +1189,7 @@ static int multilingual_handler(struct Body *a, struct State *s) struct Body *b = NULL; bool mustfree = false; int rc = 0; - struct Body *first_multilingual_part = NULL; + struct Body *first_part = NULL; struct Body *zxx_part = NULL; char *lang; @@ -1226,8 +1226,8 @@ static int multilingual_handler(struct Body *a, struct State *s) { if (mutt_can_decode(b)) { - if (b->language && !first_multilingual_part) - first_multilingual_part = b; + if (!first_part) + first_part = b; if (b->language && (mutt_str_strcmp("zxx", b->language) == 0)) zxx_part = b; @@ -1264,7 +1264,7 @@ static int multilingual_handler(struct Body *a, struct State *s) if (zxx_part) mutt_body_handler(zxx_part, s); else - mutt_body_handler(first_multilingual_part, s); + mutt_body_handler(first_part, s); } if (mustfree) diff --git a/mutt/mime.c b/mutt/mime.c index fb99cb7ce..f65c39324 100644 --- a/mutt/mime.c +++ b/mutt/mime.c @@ -27,7 +27,10 @@ */ #include "config.h" +#include #include "mime.h" +#include "memory.h" +#include "string2.h" // clang-format off /** @@ -43,6 +46,36 @@ const int IndexHex[128] = { -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1 }; + +/** + * BodyLanguages - Common MIME body languages + */ +const char *const BodyLanguages[] = { + "cs-cz", + "da", "da-dk", + "de", "de-at", "de-ch", "de-de", + "el", "el-gr", + "en", "en-au", "en-ca", "en-dk", "en-fi", "en-gb", "en-id", "en-ie", "en-il", + "en-in", "en-my", "en-no", "en-nz", "en-ph", "en-sg", "en-th", "en-us", "en-ww", + "en-xa", "en-za", + "es", "es-ar", "es-cl", "es-co", "es-es", "es-la", "es-mx", "es-pr", "es-us", + "fi", "fi-fi", + "fr", "fr-be", "fr-ca", "fr-ch", "fr-fr", "fr-lu", + "he", "he-il", + "hu", "hu-hu", + "it", "it-it", + "ja", "ja-jp", + "ko", "ko-kr", + "nl", "nl-be", "nl-nl", + "no", "no-no", + "pl", "pl-pl", + "pt", "pt-br", "pt-pt", + "ru", "ru-ru", + "sl", "sl-sl", + "sv", "sv-se", + "tr", "tr-tr", + "zh", "zh-cn", "zh-hk", "zh-tw", +}; // clang-format on /** @@ -65,3 +98,21 @@ const char *const BodyEncodings[] = { * MimeSpecials - Characters that need special treatment in MIME */ const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t"; + +/** + * mutt_mime_valid_language - Is it a valid language code? + * @param lang Language code to check (e.g., en) + * @retval true Valid language code + * + * @note Currently this checking does not strictly adhere to RFC3282 and + * RFC5646. See #BodyLanguages for all supported languages. + */ +bool mutt_mime_valid_language(const char *lang) +{ + for (size_t i = 0; i < mutt_array_size(BodyLanguages); i++) + { + if (mutt_str_strcasecmp(lang, BodyLanguages[i]) == 0) + return true; + } + return false; +} diff --git a/mutt/mime.h b/mutt/mime.h index 12e2aa1bd..0051b8690 100644 --- a/mutt/mime.h +++ b/mutt/mime.h @@ -23,6 +23,8 @@ #ifndef _MUTT_MIME_H #define _MUTT_MIME_H +#include + /** * enum ContentType - Content-Type */ @@ -70,8 +72,11 @@ enum ContentDisposition extern const int IndexHex[128]; extern const char *const BodyTypes[]; extern const char *const BodyEncodings[]; +extern const char *const BodyLanguages[]; extern const char MimeSpecials[]; +bool mutt_mime_valid_language(const char *lang); + #define hexval(c) IndexHex[(unsigned int) (c)] #define is_multipart(x) \ diff --git a/opcodes.h b/opcodes.h index 5ca79bb79..ac891da54 100644 --- a/opcodes.h +++ b/opcodes.h @@ -56,6 +56,7 @@ _fmt(OP_COMPOSE_EDIT_FOLLOWUP_TO, N_("edit the Followup-To field")) \ _fmt(OP_COMPOSE_EDIT_FROM, N_("edit the from field")) \ _fmt(OP_COMPOSE_EDIT_HEADERS, N_("edit the message with headers")) \ + _fmt(OP_COMPOSE_EDIT_LANGUAGE, N_("edit the Content-Language of the attachment")) \ _fmt(OP_COMPOSE_EDIT_MESSAGE, N_("edit the message")) \ _fmt(OP_COMPOSE_EDIT_MIME, N_("edit attachment using mailcap entry")) \ _fmt(OP_COMPOSE_EDIT_NEWSGROUPS, N_("edit the newsgroups list")) \ @@ -65,6 +66,7 @@ _fmt(OP_COMPOSE_EDIT_X_COMMENT_TO, N_("edit the X-Comment-To field")) \ _fmt(OP_COMPOSE_GET_ATTACHMENT, N_("get a temporary copy of an attachment")) \ _fmt(OP_COMPOSE_GROUP_ALTS, N_("group tagged attachments as multipart/alternative")) \ + _fmt(OP_COMPOSE_GROUP_LINGUAL, N_("group tagged attachments as multipart/multilingual")) \ _fmt(OP_COMPOSE_ISPELL, N_("run ispell on the message")) \ _fmt(OP_COMPOSE_MOVE_DOWN, N_("move an attachment down in the attachment list")) \ _fmt(OP_COMPOSE_MOVE_UP, N_("move an attachment up in the attachment list")) \ diff --git a/sendlib.c b/sendlib.c index 76cb6841d..e044725fa 100644 --- a/sendlib.c +++ b/sendlib.c @@ -355,6 +355,9 @@ int mutt_write_mime_header(struct Body *a, FILE *f) fputc('\n', f); + if (a->language) + fprintf(f, "Content-Language: %s\n", a->language); + if (a->description) fprintf(f, "Content-Description: %s\n", a->description); -- 2.40.0