/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 2003-2021 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 . */ /* MH mhn command */ #include #include #include #include static char prog_doc[] = N_("manipulate MIME messages"); static char args_doc[] = N_("[MSGLIST]"); typedef struct _msg_part *msg_part_t; static msg_part_t msg_part_create (size_t num); static void msg_part_destroy (msg_part_t p); static int msg_part_eq (msg_part_t a, msg_part_t b); static void msg_part_incr (msg_part_t p); static void msg_part_decr (msg_part_t p); static void msg_part_set_subpart (msg_part_t p, size_t subpart); static void msg_part_print (msg_part_t p, int width); static msg_part_t msg_part_parse (char const *str); static int msg_part_level (msg_part_t p); static size_t msg_part_subpart (msg_part_t p, int level); static int compose_option; static int list_option; static int show_option; static int store_option; static int headers_option = 1; static int realsize_option; static int auto_option; /*static int serialonly_option;*/ static int verbose_option; static int quiet_option; static int pause_option = -1; /* -pause option. -1 means "not given" */ static char *formfile; static char *content_type; static char *content_subtype; static char *input_file; static int width = 80; static char *charset; /* Charset for output file names. NULL means no recoding is necessary. */ static mu_msgset_t msgset; static mu_mailbox_t mbox; static mu_message_t message; static msg_part_t req_part; /* Show flags */ #define MHN_EXCLUSIVE_EXEC 001 #define MHN_STDIN 002 #define MHN_LISTING 004 #define MHN_PAUSE 010 void sfree (char **ptr) { if (!*ptr) return; free (*ptr); *ptr = NULL; } void split_content (const char *content, char **type, char **subtype) { char *p = strchr (content, '/'); if (p) { int len = p - content; *type = mu_alloc (len + 1); memcpy (*type, content, len); (*type)[len] = 0; p++; *subtype = mu_alloc (strlen (p) + 1); strcpy (*subtype, p); } else { *type = mu_alloc (strlen (content) + 1); strcpy (*type, content); *subtype = NULL; } } static void split_args (const char *argstr, size_t len, size_t *pargc, char ***pargv) { struct mu_wordsplit ws; ws.ws_delim = ";"; if (mu_wordsplit_len (argstr, len, &ws, (MU_WRDSF_DEFFLAGS & ~MU_WRDSF_QUOTE) | MU_WRDSF_DELIM | MU_WRDSF_WS)) { mu_error (_("cannot split line `%s': %s"), argstr, mu_wordsplit_strerror (&ws)); *pargc = 0; *pargv = NULL; } else { mu_wordsplit_get_words (&ws, pargc, pargv); mu_wordsplit_free (&ws); } } int _get_content_type (mu_header_t hdr, char **value, char **rest) { char *type = NULL; mu_header_aget_value_unfold (hdr, MU_HEADER_CONTENT_TYPE, &type); if (type == NULL || *type == '\0') { if (type) free (type); type = mu_strdup ("text/plain"); /* Default. */ if (rest) *rest = NULL; } else { char *p = strchr (type, ';'); if (p) *p++ = 0; if (rest) *rest = p; } *value = type; return 0; } static int _get_content_encoding (mu_header_t hdr, char **value) { char *encoding = NULL; mu_header_aget_value_unfold (hdr, MU_HEADER_CONTENT_TRANSFER_ENCODING, &encoding); if (encoding == NULL || *encoding == '\0') { if (encoding) free (encoding); encoding = mu_strdup ("7bit"); /* Default. */ } *value = encoding; return 0; } static void set_part (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { req_part = msg_part_parse (arg); } static void set_type (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { sfree (&content_type); sfree (&content_subtype); split_content (arg, &content_type, &content_subtype); } static struct mu_option options[] = { { "folder", 0, N_("FOLDER"), MU_OPTION_DEFAULT, N_("specify folder to operate upon"), mu_c_string, NULL, mh_opt_set_folder }, { "file", 0, N_("FILE"), MU_OPTION_DEFAULT, N_("specify file to operate upon"), mu_c_string, &input_file }, MU_OPTION_GROUP (N_("MIME editing options")), { "compose", 0, NULL, MU_OPTION_DEFAULT, N_("compose the MIME message (default)"), mu_c_bool, &compose_option }, { "build", 0, NULL, MU_OPTION_ALIAS }, MU_OPTION_GROUP (N_("Listing options")), { "list", 0, NULL, MU_OPTION_DEFAULT, N_("list the table of contents"), mu_c_bool, &list_option }, { "headers", 0, NULL, MU_OPTION_DEFAULT, N_("print the banner above the listing"), mu_c_bool, &headers_option }, { "realsize", 0, NULL, MU_OPTION_DEFAULT, N_("list the decoded sizes"), mu_c_bool, &realsize_option }, MU_OPTION_GROUP (N_("Display options")), { "show", 0, NULL, MU_OPTION_DEFAULT, N_("display the contents of the messages"), mu_c_bool, &show_option }, { "serialonly", 0, NULL, MU_OPTION_HIDDEN, "", mu_c_bool, NULL, mh_opt_notimpl_warning }, { "form", 0, N_("FILE"), MU_OPTION_DEFAULT, N_("read mhl format from FILE"), mu_c_string, &formfile, mh_opt_find_file }, { "pause", 0, NULL, MU_OPTION_DEFAULT, N_("pause prior to displaying content"), mu_c_bool, &pause_option }, MU_OPTION_GROUP (N_("Saving options")), { "store", 0, NULL, MU_OPTION_DEFAULT, N_("write extracted message parts to disk"), mu_c_bool, &store_option }, { "auto", 0, NULL, MU_OPTION_DEFAULT, N_("use filenames from the content headers"), mu_c_bool, &auto_option }, { "charset", 0, N_("NAME"), MU_OPTION_DEFAULT, N_("use this charset to represent attachment file names"), mu_c_string, &charset }, MU_OPTION_GROUP (N_("Other options")), { "part", 0, N_("PART"), MU_OPTION_DEFAULT, N_("limit the scope of the operation to the given part"), mu_c_string, NULL, set_part }, { "type", 0, N_("CONTENT"), MU_OPTION_DEFAULT, N_("operate on message part with given multipart content"), mu_c_string, NULL, set_type }, { "verbose", 0, NULL, MU_OPTION_DEFAULT, N_("print additional information"), mu_c_bool, &verbose_option }, { "quiet", 0, NULL, MU_OPTION_DEFAULT, N_("be quiet"), mu_c_bool, &quiet_option }, MU_OPTION_END }; /* *********************** Message part functions ************************* */ struct _msg_part { int level; int maxlevel; size_t *part; }; msg_part_t msg_part_create (size_t num) { msg_part_t p = mu_alloc (sizeof (*p)); p->maxlevel = 16; p->part = mu_alloc (sizeof (*p->part) * p->maxlevel); p->part[0] = num; p->level = 0; return p; } void msg_part_destroy (msg_part_t p) { free (p->part); free (p); } int msg_part_eq (msg_part_t a, msg_part_t b) { int i, level = a->level < b->level ? a->level : b->level; for (i = 1; i <= level; i++) if (a->part[i] != b->part[i]) return 1; return 0; } void msg_part_incr (msg_part_t p) { if (p->level == p->maxlevel) { p->maxlevel += 16; p->part = mu_realloc (p->part, sizeof (*p->part) * p->maxlevel); } p->level++; } void msg_part_decr (msg_part_t p) { if (p->level <= 0) abort (); p->level--; } void msg_part_set_subpart (msg_part_t p, size_t subpart) { p->part[p->level] = subpart; } void msg_part_print (msg_part_t p, int max_width) { int i; int width = 0; for (i = 1; i <= p->level; i++) { if (i > 1) { printf ("."); width++; } width += printf ("%s", mu_umaxtostr (0, p->part[i])); } for (; width < max_width; width++) putchar (' '); } char * msg_part_format (msg_part_t p) { int i; int width = 0; char *str, *s; for (i = 1; i <= p->level; i++) { if (i > 1) width++; width += strlen (mu_umaxtostr (0, p->part[i])); } str = s = mu_alloc (width + 1); for (i = 1; i <= p->level; i++) { if (i > 1) *s++ = '.'; s += sprintf (s, "%s", mu_umaxtostr (0, p->part[i])); } *s = 0; return str; } void msg_part_format_pool (mu_opool_t pool, msg_part_t p) { int i; for (i = 1; i <= p->level; i++) { int len; const char *buf; if (i > 1) mu_opool_append_char (pool, '.'); buf = mu_umaxtostr (0, p->part[i]); len = strlen (buf); mu_opool_append (pool, buf, len); } } msg_part_t msg_part_parse (char const *str) { msg_part_t p = msg_part_create (0); do { char *endp; size_t num = strtoul (str, &endp, 10); switch (*endp) { case '.': str = endp + 1; break; case 0: str = endp; break; default: mu_error (_("malformed part specification (near %s)"), endp); exit (1); } msg_part_incr (p); msg_part_set_subpart (p, num); } while (*str); return p; } int msg_part_level (msg_part_t p) { return p->level; } size_t msg_part_subpart (msg_part_t p, int level) { if (level <= p->level) return p->part[level]; return 0; } /* *********************** Context file accessors ************************* */ const char * _mhn_profile_get (const char *prefix, const char *type, const char *subtype, const char *defval) { char *name; const char *str = NULL; if (subtype) { mu_asprintf (&name, "mhn-%s-%s/%s", prefix, type, subtype); str = mh_global_profile_get (name, NULL); free (name); } if (!str) { mu_asprintf (&name, "mhn-%s-%s", prefix, type); str = mh_global_profile_get (name, defval); free (name); } return str; } char * mhn_compose_command (char *typestr, char *typeargs, int *flags, char *file) { const char *p, *str; char *type, *subtype, **typeargv = NULL; size_t typeargc = 0; mu_opool_t pool; split_content (typestr, &type, &subtype); str = _mhn_profile_get ("compose", type, subtype, NULL); if (!str) return NULL; /* Expand macro-notations: %a additional arguments %f filename containing content %F %f, and stdout is not redirected %s subtype */ mu_opool_create (&pool, MU_OPOOL_ENOMEMABRT); p = mu_str_skip_class (str, MU_CTYPE_SPACE); if (*p == '|') p++; for ( ; *p; p++) { if (*p == '%') { switch (*++p) { case 'a': /* additional arguments */ if (typeargs) { size_t i; if (!typeargv) split_args (typeargs, strlen (typeargs), &typeargc, &typeargv); for (i = 0; i < typeargc; i++) { if (i > 0) mu_opool_append_char (pool, ' '); mu_opool_appendz (pool, typeargv[i]); } } break; case 'F': /* %f, and stdout is not redirected */ *flags |= MHN_STDIN; /*FALLTHRU*/ case 'f': mu_opool_appendz (pool, file); break; case 's': /* subtype */ mu_opool_appendz (pool, subtype); break; default: mu_opool_append_char (pool, *p); p++; } } else mu_opool_append_char (pool, *p); } mu_opool_append_char (pool, 0); free (type); free (subtype); mu_argcv_free (typeargc, typeargv); str = mu_opool_finish (pool, NULL); p = mu_str_skip_class (str, MU_CTYPE_SPACE); if (!*p) str = NULL; else str = mu_strdup (p); mu_opool_destroy (&pool); return (char*) str; } static void mhn_tempfile_name (char **tempfile, const char *type, const char *subtype) { struct mu_tempfile_hints hints; int flags = 0, rc; const char *s = _mhn_profile_get ("suffix", type, subtype, NULL);; if (s) { hints.suffix = (char*) s; flags |= MU_TEMPFILE_SUFFIX; } rc = mu_tempfile (&hints, flags, NULL, tempfile); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_tempfile", NULL, rc); exit (1); } } static int check_type (const char *typeargs, const char *typeval) { int rc = 1; if (typeargs) { size_t i, argc; char **argv; split_args (typeargs, strlen (typeargs), &argc, &argv); for (i = 0; i < argc; i++) { if (strlen (argv[i]) > 5 && memcmp (argv[i], "type=", 5) == 0 && mu_c_strcasecmp (argv[i] + 5, typeval) == 0) { rc = 0; break; } } mu_argcv_free (argc, argv); } return rc; } char * mhn_show_command (mu_message_t msg, msg_part_t part, int *flags, char **tempfile) { const char *p, *str, *tmp; char *typestr, *type, *subtype, *typeargs; mu_opool_t pool; mu_header_t hdr; char *temp_cmd = NULL; size_t typeargc = 0; char **typeargv = NULL; mu_message_get_header (msg, &hdr); _get_content_type (hdr, &typestr, &typeargs); split_content (typestr, &type, &subtype); str = _mhn_profile_get ("show", type, subtype, NULL); if (!str) { if (mu_c_strcasecmp (typestr, "text/plain") == 0 /* FIXME: The following one should produce %pshow -file '%F' */ || mu_c_strcasecmp (typestr, "message/rfc822") == 0) { int rc; const char *moreproc = mh_global_profile_get ("moreproc", getenv ("PAGER")); if (!moreproc) return NULL; rc = mu_asprintf (&temp_cmd, "%%p%s '%%F'", moreproc); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_asprintf", NULL, rc); return NULL; } str = temp_cmd; } else if (mu_c_strcasecmp (typestr, "application/octet-stream") == 0 && check_type (typeargs, "tar") == 0) /* Use temporary file to give tar a chance to select appropriate decompressor, if the archive is compressed. */ str = "tar tvf '%F'"; else { char *parts = msg_part_format (part); /* FIXME: This message should in fact occupy two lines (exactly as split below), but mu_error malfunctions if a \n appears within the format string */ mu_error (_("don't know how to display content" " (content %s in message %lu, part %s)"), typestr, (unsigned long)part->part[0], parts); free (parts); return NULL; } } /* Expand macro-notations: %a additional arguments %e exclusive execution %f filename containing content %F %e, %f, and stdin is terminal not content %l display listing prior to displaying content %p %l, and ask for confirmation %s subtype %d content description */ mu_opool_create (&pool, MU_OPOOL_ENOMEMABRT); p = mu_str_skip_class (str, MU_CTYPE_SPACE); if (*p == '|') p++; for ( ; *p; p++) { if (*p == '%') { switch (*++p) { case 'a': /* additional arguments */ if (typeargs) { int i; if (!typeargv) split_args (typeargs, strlen (typeargs), &typeargc, &typeargv); for (i = 0; i < typeargc; i++) { if (i > 0) mu_opool_append_char (pool, ' '); mu_opool_appendz (pool, typeargv[i]); } } break; case 'e': /* exclusive execution */ *flags |= MHN_EXCLUSIVE_EXEC; break; case 'f': /* filename containing content */ if (!*tempfile) mhn_tempfile_name (tempfile, type, subtype); mu_opool_appendz (pool, *tempfile); break; case 'F': /* %e, %f, and stdin is terminal not content */ *flags |= MHN_STDIN|MHN_EXCLUSIVE_EXEC; if (!*tempfile) mhn_tempfile_name (tempfile, type, subtype); mu_opool_appendz (pool, *tempfile); break; case 'l': /* display listing prior to displaying content */ *flags |= MHN_LISTING; break; case 'p': /* %l, and ask for confirmation */ *flags |= MHN_LISTING|MHN_PAUSE; break; case 's': /* subtype */ mu_opool_appendz (pool, subtype); break; case 'd': /* content description */ if (mu_header_sget_value (hdr, MU_HEADER_CONTENT_DESCRIPTION, &tmp) == 0) mu_opool_appendz (pool, tmp); break; default: mu_opool_append_char (pool, *p); p++; } } else mu_opool_append_char (pool, *p); } mu_opool_append_char (pool, 0); free (typestr); free (type); free (subtype); free (temp_cmd); mu_argcv_free (typeargc, typeargv); str = mu_opool_finish (pool, NULL); p = mu_str_skip_class (str, MU_CTYPE_SPACE); if (!*p) str = NULL; else str = mu_strdup (p); mu_opool_destroy (&pool); return (char*) str; } enum store_destination { store_to_folder, store_to_folder_msg, store_to_file, store_to_command, store_to_stdout }; enum store_destination mhn_store_command (mu_message_t msg, msg_part_t part, const char *name, char **return_string) { const char *p, *str, *tmp; char *typestr, *type, *subtype, *typeargs; mu_opool_t pool; mu_header_t hdr; enum store_destination dest; mu_message_get_header (msg, &hdr); _get_content_type (hdr, &typestr, &typeargs); split_content (typestr, &type, &subtype); str = _mhn_profile_get ("store", type, subtype, NULL); if (!str) { if (mu_c_strcasecmp (type, "message") == 0) { /* If the content is not application/octet-stream, then mhn will check to see if the content is a message. If so, mhn will use the value "+". */ *return_string = mu_strdup (mh_current_folder ()); return store_to_folder_msg; } else if (mu_c_strcasecmp (typestr, "application/octet-stream") == 0 && check_type (typeargs, "tar") == 0) /* [If the mhn-store component] isn't found, mhn will check to see if the content is application/octet-stream with parameter "type=tar". If so, mhn will choose an appropriate filename. */ str = "%m%P.tar"; else str = "%m%P.%s"; } switch (str[0]) { case '+': if (str[1]) *return_string = mu_strdup (str); else *return_string = mu_strdup (mh_current_folder ()); return store_to_folder; case '-': *return_string = NULL; return store_to_stdout; case '|': dest = store_to_command; str = mu_str_skip_class (str + 1, MU_CTYPE_SPACE); break; default: dest = store_to_file; } /* Expand macro-notations: %m message number %P .part %p part %s subtype */ mu_opool_create (&pool, MU_OPOOL_ENOMEMABRT); for (p = str; *p; p++) { if (*p == '%') { switch (*++p) { case 'a': /* additional arguments */ mu_opool_appendz (pool, typeargs); break; case 'm': if (name) mu_opool_appendz (pool, name); else { const char *buf = mu_umaxtostr (0, msg_part_subpart (part, 0)); mu_opool_appendz (pool, buf); } break; case 'P': if (msg_part_level (part) >= 1) mu_opool_append_char (pool, '.'); /*FALLTHRU*/ case 'p': if (msg_part_level (part) >= 1) msg_part_format_pool (pool, part); break; case 's': /* subtype */ mu_opool_appendz (pool, subtype); break; case 'd': /* content description */ if (mu_header_sget_value (hdr, MU_HEADER_CONTENT_DESCRIPTION, &tmp) == 0) mu_opool_appendz (pool, tmp); break; default: mu_opool_append_char (pool, *p); p++; } } else mu_opool_append_char (pool, *p); } mu_opool_append_char (pool, *p); free (typestr); free (type); free (subtype); str = mu_opool_finish (pool, NULL); p = mu_str_skip_class (str, MU_CTYPE_SPACE); if (!*p) *return_string = NULL; else *return_string = mu_strdup (p); mu_opool_destroy (&pool); return dest; } /* ************************* Auxiliary functions ************************** */ int _message_is_external_body (mu_message_t msg, char ***env) { int rc; mu_header_t hdr; char *typestr, *argstr, *type, *subtype; if (mu_message_get_header (msg, &hdr)) return 0; _get_content_type (hdr, &typestr, &argstr); split_content (typestr, &type, &subtype); rc = subtype && strcmp (subtype, "external-body") == 0; if (rc && env) { size_t c; split_args (argstr, strlen (argstr), &c, env); } free (typestr); free (type); free (subtype); return rc; } char * _get_env (char **env, char *name) { int nlen = strlen (name); for (; *env; env++) { int len = strlen (*env); if (nlen < len && (*env)[len+1] == '=' && mu_c_strncasecmp (*env, name, nlen) == 0) return *env + len + 1; } return NULL; } void _free_env (char **env) { char **p; for (p = env; *p; p++) free (*p); free (env); } /* FIXME: Use mimehdr.c functions instead */ int get_extbody_params (mu_message_t msg, char **content, char **descr) { int rc = 0; mu_body_t body = NULL; mu_stream_t stream = NULL; char buf[128]; size_t n; mu_message_get_body (msg, &body); mu_body_get_streamref (body, &stream); while (rc == 0 && mu_stream_readline (stream, buf, sizeof buf, &n) == 0 && n > 0) { char *p; int len = strlen (buf); if (len > 0 && buf[len-1] == '\n') buf[len-1] = 0; if (descr && mu_c_strncasecmp (buf, MU_HEADER_CONTENT_DESCRIPTION ":", sizeof (MU_HEADER_CONTENT_DESCRIPTION)) == 0) { p = mu_str_skip_class (buf + sizeof (MU_HEADER_CONTENT_DESCRIPTION), MU_CTYPE_SPACE); *descr = mu_strdup (p); } else if (content && mu_c_strncasecmp (buf, MU_HEADER_CONTENT_TYPE ":", sizeof (MU_HEADER_CONTENT_TYPE)) == 0) { char *q; p = mu_str_skip_class (buf + sizeof (MU_HEADER_CONTENT_TYPE), MU_CTYPE_SPACE); q = strchr (p, ';'); if (q) *q = 0; *content = mu_strdup (p); } } mu_stream_destroy (&stream); return 0; } /* ************************** Message iterators *************************** */ typedef int (*msg_handler_t) (mu_message_t msg, msg_part_t part, char *type, char *encoding, void *data); int match_content (char *content) { int rc; char *type, *subtype; if (!content_type && !content_subtype) return 0; if (!content) return 0; split_content (content, &type, &subtype); if ((rc = mu_c_strcasecmp (content_type, type)) == 0) { if (content_subtype) rc = mu_c_strcasecmp (content_subtype, subtype); } else rc = mu_c_strcasecmp (content_type, subtype); free (type); free (subtype); return rc; } int call_handler (mu_message_t msg, msg_part_t part, char *type, char *encoding, msg_handler_t fun, void *data) { if (req_part && msg_part_eq (req_part, part)) return 0; if (match_content (type)) return 0; return fun (msg, part, type, encoding, data); } int handle_message (mu_message_t msg, msg_part_t part, msg_handler_t fun, void *data) { mu_header_t hdr; char *type = NULL; char *encoding; int ismime = 0; mu_message_get_header (msg, &hdr); _get_content_type (hdr, &type, NULL); _get_content_encoding (hdr, &encoding); fun (msg, part, type, encoding, data); sfree (&type); sfree (&encoding); mu_message_is_multipart (msg, &ismime); if (ismime) { size_t i, nparts; int rc; rc = mu_message_get_num_parts (msg, &nparts); if (rc) { mu_diag_funcall (MU_DIAG_ERR, "mu_message_get_num_parts", NULL, rc); return rc; } msg_part_incr (part); for (i = 1; i <= nparts; i++) { mu_message_t message = NULL; if (mu_message_get_part (msg, i, &message) == 0) { mu_message_get_header (message, &hdr); _get_content_type (hdr, &type, NULL); _get_content_encoding (hdr, &encoding); msg_part_set_subpart (part, i); if (mu_message_is_multipart (message, &ismime) == 0 && ismime) handle_message (message, part, fun, data); else call_handler (message, part, type, encoding, fun, data); sfree (&type); sfree (&encoding); } } msg_part_decr (part); } return 0; } int mhn_message_size (mu_message_t msg, mu_off_t *psize) { int rc; size_t size; mu_body_t body; *psize = 0; mu_message_get_body (msg, &body); if (realsize_option) { mu_stream_t dstr = NULL, bstr = NULL; if (mu_body_get_streamref (body, &bstr) == 0) { mu_header_t hdr; char *encoding; mu_message_get_header (msg, &hdr); _get_content_encoding (hdr, &encoding); rc = mu_filter_create (&dstr, bstr, encoding, MU_FILTER_DECODE, MU_STREAM_READ); free (encoding); if (rc == 0) { mu_stream_stat_buffer stat; mu_stream_t null; mu_nullstream_create (&null, MU_STREAM_WRITE); mu_stream_set_stat (null, MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUT), stat); mu_stream_copy (null, dstr, 0, NULL); mu_stream_destroy (&null); mu_stream_destroy (&dstr); *psize = stat[MU_STREAM_STAT_OUT]; return 0; } mu_stream_destroy (&bstr); } } rc = mu_body_size (body, &size); if (rc == 0) *psize = size; return rc; } /* ***************************** List Mode ******************************* */ int list_handler (mu_message_t msg, msg_part_t part, char *type, char *encoding, void *data) { mu_off_t size; mu_header_t hdr; if (msg_part_level (part) == 0) printf ("%4lu ", (unsigned long) msg_part_subpart (part, 0)); else { printf (" "); msg_part_print (part, 4); putchar (' '); } printf ("%-25s", type); mhn_message_size (msg, &size); if (size < 1024) printf (" %4lu", (unsigned long) size); else if (size < 1024*1024) printf ("%4luK", (unsigned long) (size + 1024 - 1) / 1024); else printf ("%4luM", (unsigned long) (size + 1024*1024 - 1) / 1024 / 1024); if (mu_message_get_header (msg, &hdr) == 0) { const char *descr; if (mu_header_sget_value (hdr, "Content-Description", &descr) == 0) printf (" %s", descr); } printf ("\n"); if (_message_is_external_body (msg, NULL)) { char *content_type = NULL; char *content_descr = NULL; get_extbody_params (msg, &content_type, &content_descr); printf (" "); printf ("%-25s", mu_prstr (content_type)); if (content_descr) printf (" %s", content_descr); printf ("\n"); free (content_type); free (content_descr); } return 0; } int list_message (mu_message_t msg) { size_t uid; msg_part_t part; mh_message_number (msg, &uid); part = msg_part_create (uid); handle_message (msg, part, list_handler, NULL); msg_part_destroy (part); return 0; } int list_iterator (size_t num, mu_message_t msg, void *data) { return list_message (msg); } int mhn_list (void) { int rc; if (headers_option) printf (_(" msg part type/subtype size description\n")); if (message) rc = list_message (message); else rc = mu_msgset_foreach_message (msgset, list_iterator, NULL); return rc; } /* ***************************** Show Mode ******************************* */ static mu_list_t mhl_format; int show_internal (mu_message_t msg, msg_part_t part, char *encoding, mu_stream_t out) { int rc; mu_body_t body = NULL; mu_stream_t dstr, bstr; if ((rc = mu_message_get_body (msg, &body))) { mu_error (_("%s: cannot get message body: %s"), mu_umaxtostr (0, msg_part_subpart (part, 0)), mu_strerror (rc)); return 0; } mu_body_get_streamref (body, &bstr); rc = mu_filter_create (&dstr, bstr, encoding, MU_FILTER_DECODE, MU_STREAM_READ); if (rc == 0) bstr = dstr; rc = mu_stream_copy (out, bstr, 0, NULL); mu_stream_destroy (&bstr); return rc; } int mhn_exec (mu_stream_t *str, const char *cmd, int flags) { int rc = mu_command_stream_create (str, cmd, MU_STREAM_WRITE); if (rc) { mu_error (_("cannot create proc stream (command %s): %s"), cmd, mu_strerror (rc)); } return rc; } int exec_internal (mu_message_t msg, msg_part_t part, char *encoding, const char *cmd, int flags) { int rc; mu_stream_t tmp; if ((rc = mhn_exec (&tmp, cmd, flags))) return rc; show_internal (msg, part, encoding, tmp); mu_stream_destroy (&tmp); return rc; } /* FIXME: stdin is always opened when invoking subprocess, no matter what the MHN_STDIN bit is */ int mhn_run_command (mu_message_t msg, msg_part_t part, char *encoding, char *cmd, int flags, char *tempfile) { int rc = 0; if (tempfile) { /* pass content via a tempfile */ int status; mu_stream_t tmp; struct mu_wordsplit ws; ws.ws_comment = "#"; if (mu_wordsplit (cmd, &ws, MU_WRDSF_DEFFLAGS | MU_WRDSF_COMMENT)) { mu_error (_("cannot parse command line `%s': %s"), cmd, mu_wordsplit_strerror (&ws)); return ENOSYS; } rc = mu_file_stream_create (&tmp, tempfile, MU_STREAM_RDWR|MU_STREAM_CREAT); if (rc) { mu_error (_("cannot create temporary stream (file %s): %s"), tempfile, mu_strerror (rc)); mu_wordsplit_free (&ws); return rc; } show_internal (msg, part, encoding, tmp); mu_stream_destroy (&tmp); rc = mu_spawnvp (ws.ws_wordv[0], ws.ws_wordv, &status); if (status) rc = status; mu_wordsplit_free (&ws); } else rc = exec_internal (msg, part, encoding, cmd, flags); return rc; } static RETSIGTYPE sigint (int sig) { /* nothing */ } static int mhn_pause (void) { char c; int rc = 0; RETSIGTYPE (*saved_sig) (int) = signal (SIGINT, sigint); printf (_("Press to show content...")); fflush (stdout); do { fd_set set; int res; FD_ZERO (&set); FD_SET (0, &set); res = select (1, &set, NULL, NULL, NULL); if (res != 1 || read (0, &c, 1) != 1) { putchar ('\n'); rc = 1; break; } } while (c != '\n'); signal (SIGINT, saved_sig); return rc; } int show_handler (mu_message_t msg, msg_part_t part, char *type, char *encoding, void *data) { mu_stream_t out = data; char *cmd; int flags = 0; char *tempfile = NULL; int ismime; if (mu_message_is_multipart (msg, &ismime) == 0 && ismime) return 0; cmd = mhn_show_command (msg, part, &flags, &tempfile); if (!cmd) return 0; if (pause_option == 0) flags &= ~MHN_PAUSE; else if (pause_option > 0) flags |= MHN_PAUSE; if (flags & MHN_LISTING) { mu_header_t hdr = NULL; char *parts; const char *descr; mu_off_t size = 0; mhn_message_size (msg, &size); parts = msg_part_format (part); mu_stream_printf (out, _("part %5s %-24.24s %lu"), parts, type, (unsigned long) size); free (parts); if (mu_message_get_header (msg, &hdr) == 0 && mu_header_sget_value (hdr, MU_HEADER_CONTENT_DESCRIPTION, &descr) == 0) mu_stream_printf (out, " %s", descr); mu_stream_write (out, "\n", 1, NULL); mu_stream_flush (out); } if (!((flags & MHN_PAUSE) && mhn_pause ())) mhn_run_command (msg, part, encoding, cmd, flags, tempfile); free (cmd); if (tempfile) { unlink (tempfile); free (tempfile); } return 0; } int show_message (mu_message_t msg, size_t num, void *data) { msg_part_t part = msg_part_create (num); if (mhl_format) mhl_format_run (mhl_format, width, 0, MHL_DISABLE_BODY, msg, (mu_stream_t) data); handle_message (msg, part, show_handler, data); msg_part_destroy (part); return 0; } int show_iterator (size_t num, mu_message_t msg, void *data) { msg_part_t part; mh_message_number (msg, &num); part = msg_part_create (num); show_message (msg, num, data); msg_part_destroy (part); return 0; } int mhn_show (void) { int rc; mhl_format = mhl_format_compile (formfile); if (message) rc = show_message (message, 0, mu_strout); else rc = mu_msgset_foreach_message (msgset, show_iterator, mu_strout); mu_stream_flush (mu_strout); return rc; } /* ***************************** Store Mode ****************************** */ char * normalize_path (const char *cwd, char *path) { size_t len; char *pcwd = NULL; if (!path) return path; if (!cwd) cwd = pcwd = mu_getcwd (); path = mu_normalize_path (mh_safe_make_file_name (cwd, path)); len = strlen (cwd); if (strlen (path) < len || memcmp (path, cwd, len)) sfree (&path); free (pcwd); return path; } int store_handler (mu_message_t msg, msg_part_t part, char *type, char *encoding, void *data) { const char *prefix = data; char *name = NULL; char *partstr; int ismime; int rc; mu_stream_t out; const char *dir = mh_global_profile_get ("mhn-storage", NULL); enum store_destination dest = store_to_file; if (mu_message_is_multipart (msg, &ismime) == 0 && ismime) return 0; if (auto_option) { char *val; int rc = mu_message_aget_decoded_attachment_name (msg, charset, &val, NULL); if (rc == 0) { name = normalize_path (dir, val); free (val); dest = store_to_file; } else if (rc != MU_ERR_NOENT) { char *pstr = msg_part_format (part); mu_diag_output (MU_DIAG_WARNING, _("%s: cannot decode attachment name: %s"), pstr, mu_strerror (rc)); free (pstr); } } if (!name) dest = mhn_store_command (msg, part, prefix, &name); partstr = msg_part_format (part); /* Set prefix for diagnostic purposes */ if (!prefix) prefix = mu_umaxtostr (0, msg_part_subpart (part, 0)); out = NULL; rc = 0; switch (dest) { case store_to_folder_msg: case store_to_folder: { mu_mailbox_t mbox = mh_open_folder (name, MU_STREAM_RDWR|MU_STREAM_CREAT); size_t uid; mu_mailbox_uidnext (mbox, &uid); printf (_("storing message %s part %s to folder %s as message %lu\n"), prefix, partstr, name, (unsigned long) uid); if (dest == store_to_folder_msg) { mu_body_t body; mu_stream_t str; mu_message_t tmpmsg; rc = mu_message_get_body (msg, &body); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_body", NULL, rc); break; } rc = mu_body_get_streamref (body, &str); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_body_get_stream", NULL, rc); break; } rc = mu_stream_to_message (str, &tmpmsg); mu_stream_unref (str); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_to_message", NULL, rc); break; } rc = mu_mailbox_append_message (mbox, tmpmsg); mu_message_destroy (&tmpmsg, mu_message_get_owner (tmpmsg)); } else rc = mu_mailbox_append_message (mbox, msg); if (rc) mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_append_message", NULL, rc); mu_mailbox_close (mbox); mu_mailbox_destroy (&mbox); } break; case store_to_file: if (dir && name[0] != '/') { char *s = mu_make_file_name (dir, name); if (!s) { rc = ENOMEM; mu_diag_funcall (MU_DIAG_ERROR, "mu_make_file_name", NULL, rc); break; } free (name); name = s; } printf (_("storing message %s part %s as file %s\n"), prefix, partstr, name); if (!quiet_option && access (name, R_OK) == 0) { int ok; ok = mh_getyn (_("File %s already exists. Rewrite"), name); if (!ok) { free (name); return 0; } unlink (name); } rc = mu_file_stream_create (&out, name, MU_STREAM_WRITE|MU_STREAM_CREAT); if (rc) mu_error (_("cannot create output stream (file %s): %s"), name, mu_strerror (rc)); break; case store_to_command: { struct mu_prog_hints hints; struct mu_wordsplit ws; hints.mu_prog_workdir = mu_get_homedir (); ws.ws_comment = "#"; if (mu_wordsplit (name, &ws, MU_WRDSF_DEFFLAGS|MU_WRDSF_COMMENT)) { mu_error (_("cannot split line `%s': %s"), name, mu_wordsplit_strerror (&ws)); break; } printf (_("storing msg %s part %s using command (cd %s; %s)\n"), prefix, partstr, hints.mu_prog_workdir, name); rc = mu_prog_stream_create (&out, ws.ws_wordv[0], ws.ws_wordc, ws.ws_wordv, MU_PROG_HINT_WORKDIR, &hints, MU_STREAM_WRITE); mu_wordsplit_free (&ws); if (rc) mu_diag_funcall (MU_DIAG_ERROR, "mu_prog_stream_create", NULL, rc); } break; case store_to_stdout: printf (_("storing msg %s part %s to stdout\n"), prefix, partstr); rc = 0; out = mu_strout; mu_stream_ref (out); break; } if (out) { show_internal (msg, part, encoding, out); mu_stream_flush (out); mu_stream_destroy (&out); } free (name); free (partstr); return rc; } void store_message (mu_message_t msg, void *data) { size_t uid; msg_part_t part; mh_message_number (msg, &uid); part = msg_part_create (uid); handle_message (msg, part, store_handler, data); msg_part_destroy (part); } int store_iterator (size_t num, mu_message_t msg, void *data) { store_message (msg, data); return 0; } int mhn_store (void) { int rc = 0; if (message) { char *p = strrchr (input_file, '/'); if (p) p++; else p = input_file; store_message (message, p); } else rc = mu_msgset_foreach_message (msgset, store_iterator, NULL); return rc; } /* ***************************** Compose Mode **************************** */ struct compose_env { mu_stream_t input; mu_mime_t mime; size_t line; int subpart; }; size_t mhn_error_loc (struct compose_env *env) { mu_header_t hdr = NULL; size_t n = 0; mu_message_get_header (message, &hdr); mu_header_lines (hdr, &n); return n + 1 + env->line; } static int parse_brace (char **pval, char **cmd, int c, struct compose_env *env) { char *val; int len; char *rest = *cmd; char *sp = strchr (rest, c); if (!sp) { mu_error (_("%s:%lu: missing %c"), input_file, (unsigned long) mhn_error_loc (env), c); return 1; } len = sp - rest; val = mu_alloc (len + 1); memcpy (val, rest, len); val[len] = 0; *cmd = sp + 1; *pval = val; return 0; } #define isdelim(c) (c == 0 || mu_isspace (c) || strchr (";<[(", c)) #define skipws(ptr) (ptr) = mu_str_skip_class (ptr, MU_CTYPE_SPACE) int parse_content_type (struct compose_env *env, mu_opool_t pool, char **prest, char **id, char **descr) { int status = 0, stop = 0; char *rest = *prest; char *comment = NULL; while (stop == 0 && status == 0 && *rest) { skipws (rest); switch (*rest++) { case '(': if (comment) { mu_error (_("%s:%lu: comment redefined"), input_file, (unsigned long) mhn_error_loc (env)); status = 1; break; } status = parse_brace (&comment, &rest, ')', env); break; case '[': if (!descr) { mu_error (_("%s:%lu: syntax error"), input_file, (unsigned long) mhn_error_loc (env)); status = 1; break; } if (*descr) { mu_error (_("%s:%lu: description redefined"), input_file, (unsigned long) mhn_error_loc (env)); status = 1; break; } status = parse_brace (descr, &rest, ']', env); break; case '<': if (*id) { mu_error (_("%s:%lu: content id redefined"), input_file, (unsigned long) mhn_error_loc (env)); status = 1; break; } status = parse_brace (id, &rest, '>', env); break; case ';': mu_opool_append_char (pool, ';'); mu_opool_append_char (pool, ' '); skipws (rest); for (; *rest && !mu_isspace (*rest) && *rest != '='; rest++) mu_opool_append_char (pool, *rest); skipws (rest); if (*rest != '=') { mu_error (_("%s:%lu: syntax error"), input_file, (unsigned long) mhn_error_loc (env)); status = 1; break; } rest++; mu_opool_append_char (pool, '='); skipws (rest); if (*rest == '"') { mu_opool_append_char (pool, *rest); rest++; while (*rest != '"') { if (*rest == '\\') { mu_opool_append_char (pool, *rest); if (rest[1]) rest++; else break; } mu_opool_append_char (pool, *rest); rest++; } mu_opool_append_char (pool, *rest); rest++; } else for (; !isdelim (*rest); rest++) mu_opool_append_char (pool, *rest); break; default: rest--; stop = 1; break; } } if (comment) { mu_opool_append (pool, " (", 2); mu_opool_appendz (pool, comment); mu_opool_append_char (pool, ')'); free (comment); } *prest = rest; return status; } /* cmd is: type "/" subtype 0*(";" attribute "=" value) [ "(" comment ")" ] [ "<" id ">" ] [ "[" description "]" ] */ int parse_type_command (char **pcmd, struct compose_env *env, mu_header_t hdr) { int status = 0; char *sp, c; char *type = NULL; char *subtype = NULL; char *descr = NULL, *id = NULL; mu_opool_t pool; char *rest = *pcmd; skipws (rest); for (sp = rest; !isdelim (*sp); sp++) ; c = *sp; *sp = 0; split_content (rest, &type, &subtype); *sp = c; rest = sp; if (!subtype) { mu_error (_("%s:%lu: missing subtype"), input_file, (unsigned long) mhn_error_loc (env)); return 1; } mu_opool_create (&pool, MU_OPOOL_ENOMEMABRT); mu_opool_appendz (pool, type); mu_opool_append_char (pool, '/'); mu_opool_appendz (pool, subtype); status = parse_content_type (env, pool, &rest, &id, &descr); mu_opool_append_char (pool, 0); mu_header_set_value (hdr, MU_HEADER_CONTENT_TYPE, mu_opool_finish (pool, NULL), 1); mu_opool_destroy (&pool); if (!id) id = mh_create_message_id (env->subpart); mu_header_set_value (hdr, MU_HEADER_CONTENT_ID, id, 1); free (id); if (descr) { mu_header_set_value (hdr, MU_HEADER_CONTENT_DESCRIPTION, descr, 1); free (descr); } *pcmd = rest; return status; } void copy_header (mu_message_t msg, mu_header_t out) { size_t i, count; mu_header_t hdr; mu_message_get_header (msg, &hdr); mu_header_get_field_count (hdr, &count); for (i = 1; i <= count; i++) { const char *name, *value; if (mu_header_sget_field_name (hdr, i, &name) || mu_header_sget_field_value (hdr, i, &value)) continue; mu_header_append (out, name, value); } } void copy_header_to_stream (mu_message_t msg, mu_stream_t stream) { int rc; mu_header_t hdr; mu_stream_t flt, str; mu_message_get_header (msg, &hdr); mu_header_get_streamref (hdr, &str); rc = mu_filter_create (&flt, str, "HEADER", MU_FILTER_DECODE, MU_STREAM_READ); mu_stream_unref (str); if (rc) { mu_error (_("cannot open filter stream: %s"), mu_strerror (rc)); exit (1); } rc = mu_stream_copy (stream, flt, 0, NULL); if (rc) { mu_error (_("error reading headers: %s"), mu_strerror (rc)); exit (1); } mu_stream_destroy (&flt); } void finish_msg (struct compose_env *env, mu_message_t *msg) { mu_header_t hdr; if (!msg || !*msg) return; mu_message_get_header (*msg, &hdr); if (mu_header_get_value (hdr, MU_HEADER_CONTENT_TYPE, NULL, 0, NULL)) mu_header_set_value (hdr, MU_HEADER_CONTENT_TYPE, "text/plain", 1); if (mu_header_get_value (hdr, MU_HEADER_CONTENT_ID, NULL, 0, NULL)) { char *p = mh_create_message_id (env->subpart); mu_header_set_value (hdr, MU_HEADER_CONTENT_ID, p, 1); free (p); } mu_mime_add_part (env->mime, *msg); *msg = NULL; } void finish_text_msg (struct compose_env *env, mu_message_t *msg, int ascii) { if (!ascii) { int rc; mu_message_t newmsg; mu_header_t hdr; mu_body_t body; mu_stream_t input, output, fstr; mu_message_create (&newmsg, NULL); mu_message_get_header (newmsg, &hdr); copy_header (*msg, hdr); mu_header_set_value (hdr, MU_HEADER_CONTENT_TRANSFER_ENCODING, "quoted-printable", 1); mu_message_get_body (newmsg, &body); mu_body_get_streamref (body, &output); mu_message_get_body (*msg, &body); mu_body_get_streamref (body, &input); rc = mu_filter_create (&fstr, input, "quoted-printable", MU_FILTER_ENCODE, MU_STREAM_READ); if (rc == 0) { rc = mu_stream_copy (output, fstr, 0, NULL); mu_stream_destroy (&fstr); mu_message_unref (*msg); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_copy", NULL, rc); exit (1); } *msg = newmsg; } else mu_message_destroy (&newmsg, NULL); mu_stream_destroy (&input); mu_stream_destroy (&output); } finish_msg (env, msg); } #define EXTCONTENT "message/external-body" int edit_extern (char *cmd, struct compose_env *env, mu_message_t *msg, int level) { int rc; char *rest; char *id = NULL; mu_header_t hdr, hdr2; mu_body_t body; mu_stream_t in, out = NULL; mu_opool_t pool; if (!*msg) mu_message_create (msg, NULL); if ((rc = mu_header_create (&hdr2, NULL, 0)) != 0) { mu_error (_("cannot create header: %s"), mu_strerror (rc)); return 1; } rest = cmd; rc = parse_type_command (&rest, env, hdr2); mu_message_get_header (*msg, &hdr); mu_opool_create (&pool, MU_OPOOL_ENOMEMABRT); mu_opool_append (pool, EXTCONTENT, sizeof (EXTCONTENT) - 1); *--rest = ';'; /* FIXME */ rc = parse_content_type (env, pool, &rest, &id, NULL); mu_opool_append_char (pool, 0); mu_header_set_value (hdr, MU_HEADER_CONTENT_TYPE, mu_opool_finish (pool, NULL), 1); mu_opool_destroy (&pool); if (rc) return 1; mu_message_get_body (*msg, &body); mu_body_get_streamref (body, &out); if (!id) id = mh_create_message_id (env->subpart); mu_header_set_value (hdr2, MU_HEADER_CONTENT_ID, id, 1); free (id); mu_header_get_streamref (hdr2, &in); mu_stream_copy (out, in, 0, NULL); mu_stream_destroy (&in); mu_stream_close (out); mu_stream_destroy (&out); mu_header_destroy (&hdr2); finish_msg (env, msg); return 0; } int edit_forw (char *cmd, struct compose_env *env, mu_message_t *pmsg, int level) { char *id = NULL, *descr = NULL; int stop = 0, status = 0; size_t i, npart; struct mu_wordsplit ws; mu_header_t hdr; mu_mime_t mime; mu_message_t msg; const char *val; skipws (cmd); while (stop == 0 && status == 0 && *cmd) { switch (*cmd++) { case '[': if (descr) { mu_error (_("%s:%lu: description redefined"), input_file, (unsigned long) mhn_error_loc (env)); status = 1; break; } status = parse_brace (&descr, &cmd, ']', env); break; case '<': if (id) { mu_error (_("%s:%lu: content id redefined"), input_file, (unsigned long) mhn_error_loc (env)); status = 1; break; } status = parse_brace (&id, &cmd, '>', env); break; default: cmd--; stop = 1; break; } skipws (cmd); } if (status) return status; if (mu_wordsplit (cmd, &ws, MU_WRDSF_DEFFLAGS)) { mu_error (_("%s:%lu: cannot split line: %s"), input_file, (unsigned long) mhn_error_loc (env), mu_wordsplit_strerror (&ws)); return 1; } mu_mime_create (&mime, NULL, 0); if (ws.ws_wordv[0][0] == '+') { mbox = mh_open_folder (ws.ws_wordv[0], MU_STREAM_READ); i = 1; } else { mbox = mh_open_folder (mh_current_folder (), MU_STREAM_READ); i = 0; } for (npart = 1; i < ws.ws_wordc; i++, npart++) { mu_message_t input_msg, newmsg; mu_body_t body; mu_stream_t input_str, bstr; char *endp; size_t n = strtoul (ws.ws_wordv[i], &endp, 10); if (*endp) { mu_error (_("%s:%lu: malformed directive near %s"), input_file, (unsigned long) mhn_error_loc (env), endp); return 1; } if (mh_get_message (mbox, n, &input_msg) == 0) { mu_error (_("%s:%lu: no such message: %lu"), input_file, (unsigned long) mhn_error_loc (env), (unsigned long)i); return 1; } status = mu_message_get_streamref (input_msg, &input_str); if (status) { mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_streamref", NULL, status); exit (1); } status = mu_message_create (&newmsg, NULL); if (status) { mu_diag_funcall (MU_DIAG_ERROR, "mu_message_create", NULL, status); exit (1); } status = mu_message_get_body (newmsg, &body); if (status) { mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_body", NULL, status); exit (1); } status = mu_body_get_streamref (body, &bstr); if (status) { mu_diag_funcall (MU_DIAG_ERROR, "mu_body_get_streamref", NULL, status); exit (1); } status = mu_stream_copy (bstr, input_str, 0, NULL); if (status) { mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_copy", NULL, status); exit (1); } mu_stream_unref (bstr); mu_stream_unref (input_str); mu_message_get_header (newmsg, &hdr); mu_header_set_value (hdr, MU_HEADER_CONTENT_TYPE, "message/rfc822", 1); mu_mime_add_part (mime, newmsg); } mu_wordsplit_free (&ws); if (*pmsg) { mu_message_unref (*pmsg); *pmsg = NULL; } mu_mime_get_message (mime, &msg); mu_message_unref (msg); mu_message_get_header (msg, &hdr); if (npart > 2) { char *newval, *sp; mu_header_sget_value (hdr, MU_HEADER_CONTENT_TYPE, &val); sp = strchr (val, ';'); if (sp) { mu_asprintf (&newval, "multipart/digest%s", sp); mu_header_set_value (hdr, MU_HEADER_CONTENT_TYPE, newval, 1); free (newval); } } if (!id) id = mh_create_message_id (env->subpart); mu_header_set_value (hdr, MU_HEADER_CONTENT_ID, id, 1); free (id); if (descr) { mu_header_set_value (hdr, MU_HEADER_CONTENT_DESCRIPTION, descr, 1); free (descr); } finish_msg (env, &msg); return status; } int edit_modify (char *cmd, struct compose_env *env, mu_message_t *msg) { mu_header_t hdr; if (!*msg) mu_message_create (msg, NULL); mu_message_get_header (*msg, &hdr); return parse_type_command (&cmd, env, hdr); } int edit_mime (char *cmd, struct compose_env *env, mu_message_t *msg, int level) { int rc; mu_header_t hdr; mu_body_t body; mu_stream_t in, out = NULL, fstr; char *encoding; char *typestr, *typeargs; char *shell_cmd; int flags; if (!*msg) mu_message_create (msg, NULL); mu_message_get_header (*msg, &hdr); rc = parse_type_command (&cmd, env, hdr); if (rc) return 1; mu_rtrim_class (cmd, MU_CTYPE_SPACE); _get_content_type (hdr, &typestr, &typeargs); shell_cmd = mhn_compose_command (typestr, typeargs, &flags, cmd); free (typestr); /* Open the input stream, whatever it is */ if (shell_cmd) { if (mhn_exec (&in, cmd, flags)) return 1; } else if (cmd[0] == 0) { mu_error (_("%s:%lu: missing filename"), input_file, (unsigned long) mhn_error_loc (env)); finish_msg (env, msg); return 1; } else { rc = mu_file_stream_create (&in, cmd, MU_STREAM_READ); if (rc) { mu_error (_("cannot create input stream (file %s): %s"), cmd, mu_strerror (rc)); return rc; } } /* Create filter */ if (mu_header_aget_value_unfold (hdr, MU_HEADER_CONTENT_TRANSFER_ENCODING, &encoding)) { char *typestr, *type, *subtype; _get_content_type (hdr, &typestr, NULL); split_content (typestr, &type, &subtype); if (mu_c_strcasecmp (type, "message") == 0) encoding = mu_strdup ("7bit"); else if (mu_c_strcasecmp (type, "text") == 0) encoding = mu_strdup ("quoted-printable"); else encoding = mu_strdup ("base64"); mu_header_set_value (hdr, MU_HEADER_CONTENT_TRANSFER_ENCODING, encoding, 1); free (typestr); free (type); free (subtype); } rc = mu_filter_create (&fstr, in, encoding, MU_FILTER_ENCODE, MU_STREAM_READ); if (rc) { fstr = in; mu_stream_ref (in); } mu_stream_unref (in); free (encoding); mu_message_get_body (*msg, &body); mu_body_get_streamref (body, &out); rc = mu_stream_copy (out, fstr, 0, NULL); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_copy", NULL, rc); exit (1); } mu_stream_close (out); mu_stream_destroy (&out); mu_stream_destroy (&fstr); finish_msg (env, msg); return rc; } static int has_nonascii (const char *buf, size_t n) { size_t i; for (i = 0; i < n; i++) if (!mu_isascii (buf[i])) return 1; return 0; } int mhn_edit (struct compose_env *env, int level) { int status = 0; char *buf = NULL; size_t bufsize = 0, n; mu_stream_t output = NULL; mu_message_t msg = NULL; size_t line_count = 0; int ascii_buf; while (status == 0 && mu_stream_getline (env->input, &buf, &bufsize, &n) == 0 && n > 0) { env->line++; if (!msg) { mu_header_t hdr; /* Destroy old stream */ mu_stream_destroy (&output); /* Create new message */ mu_message_create (&msg, NULL); mu_message_get_header (msg, &hdr); } if (!output) { mu_body_t body; mu_message_get_body (msg, &body); mu_body_get_streamref (body, &output); mu_message_ref (msg); line_count = 0; ascii_buf = 1; /* Suppose it is ascii */ env->subpart++; } if (buf[0] == '#') { if (buf[1] == '#') { mu_stream_write (output, buf+1, n-1, NULL); line_count++; } else { char *b2 = NULL; size_t bs = 0, n2; char *tok, *sp, c; /* Collect the whole line */ while (n > 2 && buf[n-2] == '\\') { int rc = mu_stream_getline (env->input, &b2, &bs, &n2); env->line++; if (rc == 0 && n2 > 0) { if (n + n2 > bufsize) { bufsize += 128; buf = mu_realloc (buf, bufsize); } memcpy (buf + n - 2, b2, n2); n += n2 - 2; } } free (b2); if (line_count) { /* Close and append the previous part */ mu_stream_close (output); mu_stream_destroy (&output); mu_message_unref (msg); finish_text_msg (env, &msg, ascii_buf); } mu_rtrim_cset (buf, "\n"); /* Execute the directive */ tok = buf; sp = mu_str_skip_class_comp (buf, MU_CTYPE_SPACE); c = *sp; *sp = 0; if (tok[1] == 0) /* starts a new message */; else if (tok[1] == '<') { *sp = c; status = edit_modify (tok+2, env, &msg); } else if (tok[1] == '@') { *sp = c; status = edit_extern (tok + 2, env, &msg, level); } else if (strcmp (tok, "#forw") == 0) { *sp = c; status = edit_forw (sp, env, &msg, level); } else if (strcmp (tok, "#begin") == 0) { struct compose_env new_env; mu_message_t new_msg; new_env.input = env->input; new_env.line = env->line; new_env.subpart = env->subpart; mu_mime_create (&new_env.mime, NULL, 0); status = mhn_edit (&new_env, level + 1); env->line = new_env.line; env->subpart = new_env.subpart; if (status == 0) { mu_mime_get_message (new_env.mime, &new_msg); mu_mime_add_part (env->mime, new_msg); } } else if (strcmp (tok, "#end") == 0) { if (level == 0) { mu_error (_("%s:%lu: unmatched #end"), input_file, (unsigned long) mhn_error_loc (env)); status = 1; } break; } else { *sp = c; status = edit_mime (tok + 1, env, &msg, level); } } } else if (line_count > 0 || buf[0] != '\n') { if (ascii_buf && has_nonascii (buf, n)) ascii_buf = 0; mu_stream_write (output, buf, n, NULL); line_count++; } } free (buf); mu_stream_destroy (&output); if (msg) { if (line_count) finish_text_msg (env, &msg, ascii_buf); else mu_message_unref (msg); } return status; } int parse_header_directive (const char *val, char **encoding, char **charset, char **subject) { const char *p; /* Provide default values */ *encoding = NULL; *charset = NULL; *subject = NULL; if (*val != '#') { *subject = mu_strdup (val); return 1; } val++; switch (*val) { case '#': break; case '<': p = strchr (val, '>'); if (p) { size_t i, argc; char **argv; *subject = mu_strdup (p + 1); split_args (val + 1, p - val - 1, &argc, &argv); for (i = 0; i < argc; i++) { if (strlen (argv[i]) > 8 && memcmp (argv[i], "charset=", 8) == 0) { free (*charset); *charset = mu_strdup (argv[i] + 8); } else if (strlen (argv[i]) > 9 && memcmp (argv[i], "encoding=", 9) == 0) { free (*encoding); *encoding = mu_strdup (argv[i] + 9); } } mu_argcv_free (argc, argv); return 0; } break; default: val--; } *subject = mu_strdup (val); return 1; } void mhn_header (mu_message_t msg, mu_message_t omsg) { mu_header_t hdr = NULL; const char *val; mu_message_get_header (msg, &hdr); if (mu_header_sget_value (hdr, MU_HEADER_SUBJECT, &val) == 0) { char *subject, *encoding, *charset; if (parse_header_directive (val, &encoding, &charset, &subject)) { if (has_nonascii (val, strlen (val))) { int ismime = 0; mu_message_t part = NULL; mu_message_is_multipart (omsg, &ismime); if (ismime) mu_message_get_part (omsg, 1, &part); else part = omsg; if (part) { mu_header_t parthdr = NULL; char *typestr, *typeargs; mu_message_get_header (part, &parthdr); _get_content_type (parthdr, &typestr, &typeargs); _get_content_encoding (parthdr, &encoding); if (typeargs) { size_t i, argc; char **argv; split_args (typeargs, strlen (typeargs), &argc, &argv); for (i = 0; i < argc; i++) if (strlen (argv[i]) > 8 && memcmp (argv[i], "charset=", 8) == 0) { charset = mu_strdup (argv[i]+8); break; } mu_argcv_free (argc, argv); } free (typestr); } } } if (charset) { char *p; int rc; if (!encoding || strcmp (encoding, "7bit") == 0) { free (encoding); encoding = mu_strdup ("base64"); } rc = mu_rfc2047_encode (charset, encoding, subject, &p); if (rc) mu_error (_("cannot encode subject using %s, %s: %s"), charset, encoding, mu_strerror (rc)); else { mu_header_set_value (hdr, MU_HEADER_SUBJECT, p, 1); free (p); } } free (charset); free (encoding); free (subject); } } int mhn_compose (void) { int rc; mu_mime_t mime = NULL; mu_body_t body; mu_stream_t stream, in; struct compose_env env; mu_message_t msg; char *name, *backup, *p; mu_mime_create (&mime, NULL, 0); mu_message_get_body (message, &body); mu_body_get_streamref (body, &stream); env.mime = mime; env.input = stream; env.subpart = 0; env.line = 0; rc = mhn_edit (&env, 0); mu_stream_destroy (&stream); if (rc) return rc; mu_mime_get_message (mime, &msg); mu_message_unref (msg); p = strrchr (input_file, '/'); /* Prepare file names */ if (p) { *p = 0; name = mu_tempname (input_file); mu_asprintf (&backup, "%s/,%s.orig", input_file, p + 1); *p = '/'; } else { name = mu_tempname (NULL); mu_asprintf (&backup, ",%s.orig", input_file); } /* Create working draft file */ unlink (name); rc = mu_file_stream_create (&stream, name, MU_STREAM_RDWR|MU_STREAM_CREAT); if (rc) { mu_error (_("cannot create output stream (file %s): %s"), name, mu_strerror (rc)); free (name); mu_mime_unref (mime); return rc; } mhn_header (message, msg); copy_header_to_stream (message, stream); mu_message_get_streamref (msg, &in); mu_stream_copy (stream, in, 0, NULL); mu_stream_destroy (&in); mu_stream_destroy (&stream); /* Preserve the backup copy and replace the draft */ unlink (backup); rc = mu_rename_file (input_file, backup, MU_COPY_OVERWRITE); if (rc) mu_error (_("can't rename %s to backup file %s: %s"), input_file, backup, mu_strerror (rc)); else { rc = mu_rename_file (name, input_file, 0); if (rc) mu_error (_("can't rename %s to %s: %s"), name, input_file, mu_strerror (rc)); } free (name); mu_mime_unref (mime); return rc; } /* *************************** Main Entry Point ************************** */ int main (int argc, char **argv) { int rc; mh_getopt (&argc, &argv, options, 0, args_doc, prog_doc, NULL); if (!formfile) mh_find_file ("mhl.headers", &formfile); if (!isatty (0)) pause_option = 0; if (!compose_option && !list_option && !show_option && !store_option) compose_option = 1; signal (SIGPIPE, SIG_IGN); if (input_file) { if (argc) { mu_error (_("extra arguments")); return 1; } message = mh_file_to_message (NULL, mu_tilde_expansion (input_file, MU_HIERARCHY_DELIMITER, NULL)); if (!message) return 1; } if (compose_option) { if (argc > 1) { mu_error (_("extra arguments")); return 1; } if (!input_file) { input_file = mh_expand_name (mu_folder_directory (), argc == 1 ? argv[0] : "draft", NAME_ANY); message = mh_file_to_message (NULL, input_file); if (!message) return 1; } rc = mhn_compose (); } else { mbox = mh_open_folder (mh_current_folder (), MU_STREAM_READ); mh_msgset_parse (&msgset, mbox, argc, argv, "cur"); /* FIXME: Combine the three */ rc = 0; if (list_option) rc |= mhn_list (); if (show_option) rc |= mhn_show (); if (store_option) rc |= mhn_store (); } return rc ? 1 : 0; }