diff options
Diffstat (limited to 'lib/mdecode.c')
-rw-r--r-- | lib/mdecode.c | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/lib/mdecode.c b/lib/mdecode.c new file mode 100644 index 000000000..8ec48cd3c --- /dev/null +++ b/lib/mdecode.c @@ -0,0 +1,313 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 1999-2024 Free Software Foundation, Inc. + + GNU Mailutils is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + GNU Mailutils is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */ + +#include <config.h> +#include <mailutils/mailutils.h> +#include <mailutils/imaputil.h> + +static mu_list_t text_mime_list; + +static char const *default_text_types[] = { + "text/*", + "application/*shell", + "application/shellscript", + "*/x-csrc", + "*/x-csource", + "*/x-diff", + "*/x-patch", + "*/x-perl", + "*/x-php", + "*/x-python", + "*/x-sh", + NULL +}; + +static void text_mime_add (char const *s); + +struct mime_pattern +{ + char *pat_type; + char *pat_subtype; +}; + +static int +text_mime_cmp (const void *item, const void *ptr) +{ + const struct mime_pattern *pat = item; + mu_content_type_t ct = (mu_content_type_t) ptr; + if (mu_imap_wildmatch_ci (pat->pat_type, ct->type, 0) == 0) + { + if (ct->subtype == NULL) + return pat->pat_subtype != NULL; + if (pat->pat_subtype == NULL + || mu_imap_wildmatch (pat->pat_subtype, ct->subtype, '/') == 0) + return 0; + } + return 1; +} + +static void +text_mime_init (void) +{ + if (!text_mime_list) + { + int i; + int rc = mu_list_create (&text_mime_list); + if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_list_create", NULL, rc); + mu_alloc_die (); + } + mu_list_set_destroy_item (text_mime_list, mu_list_free_item); + mu_list_set_comparator (text_mime_list, text_mime_cmp); + + for (i = 0; default_text_types[i]; i++) + text_mime_add (default_text_types[i]); + } +} + +static void +text_mime_add (char const *s) +{ + int rc; + struct mime_pattern *mp; + char *p; + + text_mime_init (); + + mp = mu_alloc (sizeof *mp + strlen (s) + 1); + mp->pat_type = (char*)(mp + 1); + strcpy (mp->pat_type, s); + p = strchr (mp->pat_type, '/'); + if (p) + *p++ = 0; + mp->pat_subtype = p; + rc = mu_list_append (text_mime_list, mp); + if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_list_append", NULL, rc); + mu_alloc_die (); + } +} + +static int +cb_text_type (void *data, mu_config_value_t *val) +{ + size_t i; + + switch (val->type) + { + case MU_CFG_STRING: + text_mime_add (val->v.string); + break; + + case MU_CFG_LIST: + for (i = 0; i < val->v.arg.c; i++) + { + if (mu_cfg_assert_value_type (&val->v.arg.v[i], MU_CFG_STRING)) + return 1; + text_mime_add (val->v.arg.v[i].v.string); + } + break; + + default: + mu_error ("%s", _("expected string or list")); + } + return 0; +} + +static struct mu_cfg_param mime_param[] = { + { "text-type", mu_cfg_callback, NULL, 0, cb_text_type, + N_("Define textual mime types."), + N_("arg: pattern or list of patterns") }, + { NULL } +}; + +struct mu_cli_capa mu_cli_capa_mime = { + .name = "mime", + .cfg = mime_param +}; + +static int +is_text_part (mu_content_type_t ct) +{ + text_mime_init (); + return mu_list_locate (text_mime_list, ct, NULL) == 0; +} + +static int +charset_setup (mu_stream_t *pstr, mu_content_type_t ct, char const *charset) +{ + struct mu_mime_param *param; + if (charset + && mu_assoc_lookup (ct->param, "charset", ¶m) == 0 + && mu_c_strcasecmp (param->value, charset)) + { + mu_stream_t input = *pstr; + char const *argv[] = { "iconv", NULL, NULL, NULL }; + mu_stream_t flt; + int rc; + + argv[1] = param->value; + argv[2] = charset; + rc = mu_filter_chain_create (&flt, input, + MU_FILTER_ENCODE, + MU_STREAM_READ, + MU_ARRAY_SIZE (argv) - 1, + (char**) argv); + if (rc) + { + mu_error (_("can't convert from charset %s to %s: %s"), + param->value, charset, mu_strerror (rc)); + return rc; + } + mu_stream_unref (input); + *pstr = flt; + } + return 0; +} + +int +message_body_stream (mu_message_t msg, int unix_header, char const *charset, + mu_stream_t *pstr) +{ + int rc; + mu_header_t hdr; + mu_body_t body; + mu_stream_t d_stream; + mu_stream_t stream = NULL; + char *encoding = NULL; + char *buf; + mu_content_type_t ct; + + /* Get the headers. */ + rc = mu_message_get_header (msg, &hdr); + if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_header", NULL, rc); + return rc; + } + + /* Read and parse the Content-Type header. */ + rc = mu_header_aget_value_unfold (hdr, MU_HEADER_CONTENT_TYPE, &buf); + if (rc == MU_ERR_NOENT) + { + buf = strdup ("text/plain"); + if (!buf) + return errno; + } + else if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_header_aget_value_unfold", NULL, rc); + return rc; + } + + rc = mu_content_type_parse_ext (buf, NULL, + MU_CONTENT_TYPE_RELAXED | + MU_CONTENT_TYPE_PARAM, &ct); + if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_content_type_parse_ext", buf, rc); + free (buf); + return rc; + } + free (buf); + buf = NULL; + + if (is_text_part (ct)) + /* Process only textual parts */ + { + /* Get the body stream. */ + rc = mu_message_get_body (msg, &body); + if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_body", NULL, rc); + goto err; + } + + rc = mu_body_get_streamref (body, &stream); + if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_body_get_streamref", NULL, rc); + goto err; + } + + /* Filter it through the appropriate decoder. */ + rc = mu_header_aget_value_unfold (hdr, + MU_HEADER_CONTENT_TRANSFER_ENCODING, + &encoding); + if (rc == 0) + mu_rtrim_class (encoding, MU_CTYPE_SPACE); + else if (rc == MU_ERR_NOENT) + encoding = NULL; + else if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_header_aget_value_unfold", + NULL, rc); + goto err; + } + + if (encoding == NULL || *encoding == '\0') + /* No need to filter */; + else if ((rc = mu_filter_create (&d_stream, stream, encoding, + MU_FILTER_DECODE, MU_STREAM_READ)) == 0) + { + mu_stream_unref (stream); + stream = d_stream; + } + else if (rc == MU_ERR_NOENT) + { + mu_error ("unknown encoding: %s", encoding); + } + else + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_filter_create", encoding, rc); + /* FIXME: continue anyway? */ + } + + /* Convert the content to the requested charset. */ + rc = charset_setup (&stream, ct, charset); + if (rc) + goto err; + + if (unix_header) + { + rc = mu_filter_create (&d_stream, stream, "FROM", + MU_FILTER_ENCODE, MU_STREAM_READ); + if (rc == 0) + { + mu_stream_unref (stream); + stream = d_stream; + } + else + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_filter_create", "FROM", rc); + /* continue anyway */ + } + } + } + else + rc = MU_ERR_USER0; + err: + free (buf); + free (encoding); + mu_content_type_destroy (&ct); + if (rc == 0) + *pstr = stream; + else + mu_stream_destroy (&stream); + return rc; +} |