Commit 5a4ca213 5a4ca2135f5a57d2a7ab18bf7d4881b4caefec35 by Sergey Poznyakoff

Send multipart/alternative messages using mail.

The new option --alternative is provided to change the content type of
the composed message to multipart/alternative.  The ~/ escape toggles
the type between multipart/mixed and multipart/alternative.  In messages
of multipart/alternative type, the Content-Disposition header of all
attachments is reset to "inline".

* libmailutils/mime/mime.c (mu_mime_create): Bugfix: honour the
MU_MIME_MULTIPART_ALT flag.
* mail/mail.c: New option --alternative.
* mail/mail.h (compose_env) <attlist,mime>: New members.
(multipart_alternative): New global.
(send_attach_file): Change return value.
(escape_toggle_multipart_type): New proto.
* mail/send.c (multipart_alternative): New global.
Keep the list of attachments in compose_env_t.  Make sure it is freed
when no longer used.
Implement the ~/ escape.
* mail/table.c: New escape ~/
* mail/util.c: Use mu_strerror instead of strerror.

* NEWS: Document changes to the mail utility
* doc/texinfo/programs.texi: Likewise.
1 parent 444d20f3
GNU mailutils NEWS -- history of user-visible changes. 2017-04-09
GNU mailutils NEWS -- history of user-visible changes. 2017-04-13
Copyright (C) 2002-2017 Free Software Foundation, Inc.
See the end of file for copying conditions.
......@@ -88,6 +88,21 @@ defined. Instead, the following constants are defined in config.h:
* movemail: new option --progress-meter
* mail: sending multipart messages
** New option --alternative
When used with --attach or --attach-fd options, this option sets the
Content-Type of the constructed message to "multipart/alternative".
In the absense of this option, the type is "multipart/mixed".
** New escape ~/
New escape ~/ toggles the Content-Type of the message being composed
between "multipart/mixed" and "multipart/alternative".
The actual Content-Type is displayed by the ~l (list attachments) escape.
* scheme implementation of the Sieve language discontinued
There's no reason to keep two different implementations of the Sieve
......
......@@ -3420,12 +3420,19 @@ To list the files attached so far, use the @samp{~l} escape:
@example
~l
multipart/mixed
1 myfile.html text/html base64
@end example
Each line of the listing contains the ordinal number of the attachment,
The first line of the output shows the content type of the message.
Each subsequent line contains the ordinal number of the attachment,
the name of the file, content-type and transfer encoding used.
@cindex ~/, mail escape
The @samp{~/} escape toggles the content type bewteen
@samp{multipart/mixed}, and @samp{multipart/alternative}. The new
value of the content type is displayed on the screen.
@cindex ~^, mail escape
The @samp{~^} escape removes attachments. Its argument is the number
of the attachment to remove, e.g.:
......@@ -3568,6 +3575,12 @@ either or both of them using the @option{--content-name} and
affect only the next @option{--attach} (or @option{--attach-fd}, see
below) option.
By default, the message will be assigned the content type
@samp{multipart/mixed}. To change it to @samp{multipart/alternative},
use the @option{--alternative} command line option. Using this option
also sets the @samp{Content-Disposition} header of each attached
message to @samp{inline}.
All the examples above will enter the usual interactive shell,
allowing you to compose the body of the message. If that's not
needed, the non-interactive use can be forced by redirecting
......
......@@ -879,6 +879,9 @@ _mime_body_lines (mu_body_t body, size_t *plines)
return 0;
}
#define MIME_MULTIPART_FLAGS \
(MU_MIME_MULTIPART_MIXED|MU_MIME_MULTIPART_ALT)
int
mu_mime_create (mu_mime_t *pmime, mu_message_t msg, int flags)
{
......@@ -886,9 +889,19 @@ mu_mime_create (mu_mime_t *pmime, mu_message_t msg, int flags)
int ret = 0;
size_t size;
mu_body_t body;
if (pmime == NULL)
return EINVAL;
switch (flags & MIME_MULTIPART_FLAGS)
{
case MIME_MULTIPART_FLAGS:
return EINVAL;
case 0:
flags |= MU_MIME_MULTIPART_MIXED;
}
*pmime = NULL;
if ((mime = calloc (1, sizeof (*mime))) == NULL)
return ENOMEM;
......@@ -931,7 +944,7 @@ mu_mime_create (mu_mime_t *pmime, mu_message_t msg, int flags)
}
else
{
mime->flags |= MIME_NEW_MESSAGE | MU_MIME_MULTIPART_MIXED;
mime->flags |= MIME_NEW_MESSAGE;
}
if (ret != 0)
{
......
......@@ -146,9 +146,8 @@ cli_attach (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
arg = NULL;
fd = 0;
}
if (send_attach_file (fd, arg, content_filename, content_name,
default_content_type, default_encoding))
exit (1);
send_attach_file (fd, arg, content_filename, content_name,
default_content_type, default_encoding);
free (content_name);
content_name = NULL;
......@@ -169,9 +168,8 @@ cli_attach_fd (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
exit (po->po_exit_error);
}
if (send_attach_file (fd, NULL, content_filename, content_name,
default_content_type, default_encoding))
exit (1);
send_attach_file (fd, NULL, content_filename, content_name,
default_content_type, default_encoding);
free (content_name);
content_name = NULL;
......@@ -240,6 +238,10 @@ static struct mu_option mail_options[] = {
N_("append given header to the message being sent"),
mu_c_string, NULL, cli_append },
{ "alternative", 0, NULL, MU_OPTION_DEFAULT,
N_("force multipart/alternative content type"),
mu_c_bool, &multipart_alternative },
{ "skip-empty-attachments", 0, NULL, MU_OPTION_DEFAULT,
N_("skip attachments with empty body"),
mu_c_bool, &skip_empty_attachments },
......
......@@ -99,10 +99,13 @@ typedef int function_t (int, char **);
typedef struct compose_env
{
mu_header_t header; /* The message headers */
mu_stream_t compstr; /* Temporary compose stream */
char **outfiles; /* Names of the output files. The message is to be
saved in each of these. */
int nfiles; /* Number of output files */
mu_stream_t compstr; /* Temporary compose stream */
char **outfiles; /* Names of the output files. The message is to be
saved in each of these. */
int nfiles; /* Number of output files */
int alt; /* Use multipart/alternative type */
mu_list_t attlist; /* Attachments */
mu_mime_t mime; /* Associated MIME object */
} compose_env_t;
#define MAIL_COMMAND_COMMON_MEMBERS \
......@@ -174,7 +177,8 @@ extern const char *program_version;
extern char *default_encoding;
extern char *default_content_type;
extern int skip_empty_attachments;
extern int multipart_alternative;
/* Functions */
extern int mail_alias (int argc, char **argv);
extern int mail_alt (int argc, char **argv); /* command alternates */
......@@ -259,11 +263,11 @@ extern char *mail_expand_name (const char *name);
extern void send_append_header (char const *text);
extern void send_append_header2 (char const *name, char const *value, int mode);
extern int send_attach_file (int fd,
const char *filename,
const char *content_filename,
const char *content_name,
const char *content_type, const char *encoding);
extern void send_attach_file (int fd,
const char *filename,
const char *content_filename,
const char *content_name,
const char *content_type, const char *encoding);
extern int escape_check_args (int argc, char **argv, int minargs, int maxargs);
......@@ -292,6 +296,8 @@ extern int escape_list_attachments (int argc, char **argv,
extern int escape_attach (int argc, char **argv, compose_env_t *env);
extern int escape_remove_attachment (int argc, char **argv,
compose_env_t *env);
extern int escape_toggle_multipart_type (int argc, char **argv,
compose_env_t *env);
enum
{
......
......@@ -24,6 +24,8 @@
static int isfilename (const char *);
static int msg_to_pipe (const char *cmd, mu_message_t msg);
int multipart_alternative;
/* Additional message headers */
struct add_header
......@@ -143,8 +145,6 @@ struct atchinfo
int skip_empty;
};
static mu_list_t attlist;
static void
atchinfo_free (void *p)
{
......@@ -158,19 +158,63 @@ atchinfo_free (void *p)
free (ap);
}
static mu_list_t
attlist_new (void)
{
mu_list_t lst;
int rc = mu_list_create (&lst);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_list_create", NULL, rc);
exit (1);
}
mu_list_set_destroy_item (lst, atchinfo_free);
return lst;
}
static void
attlist_add (mu_list_t attlist, char *id, char const *encoding,
char const *content_type, char const *content_name,
char const *content_filename,
mu_stream_t stream, int skip_empty)
{
struct atchinfo *aptr;
int rc;
aptr = mu_alloc (sizeof (*aptr));
aptr->id = id ? mu_strdup (id) : id;
aptr->encoding = mu_strdup (encoding);
aptr->content_type = mu_strdup (content_type ?
content_type : "application/octet-stream");
aptr->name = content_name ? mu_strdup (content_name) : NULL;
aptr->filename = content_filename ? mu_strdup (content_filename) : NULL;
aptr->source = stream;
if (stream)
mu_stream_ref (stream);
aptr->skip_empty = skip_empty;
rc = mu_list_append (attlist, aptr);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_list_append", NULL, rc);
exit (1);
}
}
int
send_attach_file (int fd,
const char *realname,
const char *content_filename, const char *content_name,
const char *content_type, const char *encoding)
attlist_attach_file (mu_list_t *attlist_ptr,
int fd,
const char *realname,
const char *content_filename, const char *content_name,
const char *content_type, const char *encoding)
{
int rc;
struct stat st;
struct atchinfo *aptr;
mu_list_t list;
mu_stream_t stream = NULL;
char *id = NULL;
mu_list_t attlist;
if (fd >= 0)
{
rc = mu_fd_stream_create (&stream, NULL, fd, MU_STREAM_READ);
......@@ -238,32 +282,75 @@ send_attach_file (int fd,
return 1;
}
if (!attlist)
if (!*attlist_ptr)
{
rc = mu_list_create (&attlist);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_list_create", NULL, rc);
exit (1);
}
mu_list_set_destroy_item (attlist, atchinfo_free);
attlist = attlist_new ();
*attlist_ptr = attlist;
}
aptr = mu_alloc (sizeof (*aptr));
else
attlist = *attlist_ptr;
attlist_add (attlist, id, encoding, content_type,
content_name, content_filename,
stream, skip_empty_attachments);
if (stream)
mu_stream_unref (stream);
free (id);
aptr->id = id;
aptr->encoding = mu_strdup (encoding);
aptr->content_type = mu_strdup (content_type ?
content_type : "application/octet-stream");
aptr->name = content_name ? mu_strdup (content_name) : NULL;
aptr->filename = content_filename ? mu_strdup (content_filename) : NULL;
aptr->source = stream;
aptr->skip_empty = skip_empty_attachments;
rc = mu_list_append (attlist, aptr);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_list_append", NULL, rc);
exit (1);
}
return 0;
}
static int
attlist_helper (void *item, void *data)
{
struct atchinfo *aptr = item;
mu_list_t list = data;
attlist_add (list, aptr->id, aptr->encoding, aptr->content_type,
aptr->name, aptr->filename, aptr->source, aptr->skip_empty);
return 0;
}
static mu_list_t
attlist_copy (mu_list_t src)
{
mu_list_t dst;
if (!src)
return NULL;
dst = attlist_new ();
mu_list_foreach (src, attlist_helper, dst);
return dst;
}
static mu_list_t attachment_list;
void
send_attach_file (int fd,
const char *realname,
const char *content_filename, const char *content_name,
const char *content_type, const char *encoding)
{
attlist_attach_file (&attachment_list,
fd,
realname,
content_filename,
content_name,
content_type,
encoding);
}
static void
report_multipart_type (compose_env_t *env)
{
mu_printf ("multipart/%s\n", env->alt ? "alternative" : "mixed");
}
/* ~/ - toggle between multipart/mixed and multipart/alternative */
int
escape_toggle_multipart_type (int argc, char **argv, compose_env_t *env)
{
env->alt = !env->alt;
report_multipart_type (env);
return 0;
}
......@@ -273,8 +360,10 @@ escape_list_attachments (int argc, char **argv, compose_env_t *env)
mu_iterator_t itr;
int i;
if (mu_list_is_empty (attlist) ||
mu_list_get_iterator (attlist, &itr))
report_multipart_type (env);
if (mu_list_is_empty (env->attlist) ||
mu_list_get_iterator (env->attlist, &itr))
{
mu_printf ("%s\n", _("No attachments"));
return 0;
......@@ -308,8 +397,9 @@ escape_attach (int argc, char **argv, compose_env_t *env)
case 3:
content_type = argv[2];
case 2:
return send_attach_file (-1, argv[1], argv[1], argv[1],
content_type, encoding);
return attlist_attach_file (&env->attlist,
-1, argv[1], argv[1], argv[1],
content_type, encoding);
default:
return escape_check_args (argc, argv, 2, 4);
}
......@@ -332,21 +422,21 @@ escape_remove_attachment (int argc, char **argv, compose_env_t *env)
return 1;
}
mu_list_count (attlist, &count);
mu_list_count (env->attlist, &count);
if (n == 0 || n > count)
{
mu_error (_("index out of range"));
return 1;
}
return mu_list_remove_nth (attlist, n - 1);
return mu_list_remove_nth (env->attlist, n - 1);
}
static int
saveatt (void *item, void *data)
{
struct atchinfo *aptr = item;
mu_mime_t mime = data;
compose_env_t *env = data;
mu_message_t part;
mu_header_t hdr;
int rc;
......@@ -390,13 +480,15 @@ saveatt (void *item, void *data)
return 0;
}
mu_mime_get_num_parts (mime, &nparts);
mu_mime_get_num_parts (env->mime, &nparts);
mu_message_get_header (part, &hdr);
if (env->alt)
mu_header_set_value (hdr, MU_HEADER_CONTENT_DISPOSITION, "inline", 1);
mu_rfc2822_msg_id (nparts, &p);
mu_header_set_value (hdr, MU_HEADER_CONTENT_ID, p, 1);
free (p);
rc = mu_mime_add_part (mime, part);
rc = mu_mime_add_part (env->mime, part);
mu_message_unref (part);
if (rc)
{
......@@ -418,7 +510,7 @@ add_body (mu_message_t inmsg, mu_iterator_t itr, mu_mime_t mime)
int rc;
mu_message_get_body (inmsg, &body);
if (skip_empty_attachments)
if (skip_empty_attachments || multipart_alternative)
{
size_t size;
rc = mu_body_size (body, &size);
......@@ -483,7 +575,7 @@ add_body (mu_message_t inmsg, mu_iterator_t itr, mu_mime_t mime)
}
static int
add_attachments (mu_message_t *pmsg, mu_mime_t *pmime)
add_attachments (compose_env_t *env, mu_message_t *pmsg)
{
mu_message_t inmsg, outmsg;
mu_header_t inhdr, outhdr;
......@@ -491,16 +583,15 @@ add_attachments (mu_message_t *pmsg, mu_mime_t *pmime)
mu_mime_t mime;
int rc;
if (mu_list_is_empty (attlist))
{
*pmime = NULL;
return 0;
}
if (mu_list_is_empty (env->attlist))
return 0;
inmsg = *pmsg;
/* Create a mime object */
rc = mu_mime_create (&mime, NULL, 0);
rc = mu_mime_create (&mime, NULL,
env->alt ?
MU_MIME_MULTIPART_ALT : MU_MIME_MULTIPART_MIXED);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mime_create", NULL, rc);
......@@ -517,8 +608,10 @@ add_attachments (mu_message_t *pmsg, mu_mime_t *pmime)
return 1;
}
env->mime = mime;
/* Add the respective attachments */
rc = mu_list_foreach (attlist, saveatt, mime);
rc = mu_list_foreach (env->attlist, saveatt, env);
if (rc)
{
mu_mime_destroy (&mime);
......@@ -559,7 +652,6 @@ add_attachments (mu_message_t *pmsg, mu_mime_t *pmime)
mu_message_unref (inmsg);
*pmsg = outmsg;
*pmime = mime;
return 0;
}
......@@ -785,6 +877,8 @@ void
compose_init (compose_env_t *env)
{
memset (env, 0, sizeof (*env));
env->alt = multipart_alternative;
env->attlist = attlist_copy (attachment_list);
mu_list_foreach (add_header_list, seed_headers, env);
}
......@@ -873,6 +967,9 @@ compose_destroy (compose_env_t *env)
{
mu_header_destroy (&env->header);
free (env->outfiles);
mu_mime_destroy (&env->mime);
mu_list_destroy (&env->attlist);
mu_stream_destroy (&env->compstr);
}
static int
......@@ -1112,7 +1209,6 @@ mail_send0 (compose_env_t *env, int save_to)
if (rc)
{
mu_error (_("Cannot open temporary file: %s"), mu_strerror (rc));
mu_list_destroy (&attlist);
return 1;
}
......@@ -1208,8 +1304,6 @@ mail_send0 (compose_env_t *env, int save_to)
if (int_cnt)
{
save_dead_message_env (env);
mu_stream_destroy (&env->compstr);
mu_list_destroy (&attlist);
return 1;
}
......@@ -1225,7 +1319,6 @@ mail_send0 (compose_env_t *env, int save_to)
if (util_header_expand (&env->header) == 0)
{
mu_mime_t mime = NULL;
mu_message_t msg = NULL;
int status = 0;
int sendit = (compose_header_get (env, MU_HEADER_TO, NULL) ||
......@@ -1246,7 +1339,7 @@ mail_send0 (compose_env_t *env, int save_to)
mu_message_set_header (msg, env->header, NULL);
env->header = NULL;
status = add_attachments (&msg, &mime);
status = add_attachments (env, &msg);
if (status)
break;
......@@ -1323,14 +1416,10 @@ mail_send0 (compose_env_t *env, int save_to)
mu_stream_destroy (&env->compstr);
mu_message_destroy (&msg, NULL);
mu_mime_destroy (&mime);
return status;
}
else
save_dead_message_env (env);
mu_stream_destroy (&env->compstr);
mu_list_destroy (&attlist);
return 1;
}
......
......@@ -228,6 +228,7 @@ static const struct mail_escape_entry mail_escape_table[] = {
{"-", "-", "-[mail-command]", escape_command },
{"+", "+", "+name [content-type [encoding]]", escape_attach },
{"^", "^", "^N", escape_remove_attachment },
{"/", "/", "/", escape_toggle_multipart_type },
{"?", "?", "?", escape_help },
{"A", "A", "A", escape_sign },
{"a", "a", "a", escape_sign },
......
......@@ -706,7 +706,7 @@ util_save_outgoing (mu_message_t msg, char *savefile)
if (rc)
{
mu_error (_("Cannot create output mailbox `%s': %s"),
filename, strerror (rc));
filename, mu_strerror (rc));
free (filename);
return;
}
......@@ -714,13 +714,13 @@ util_save_outgoing (mu_message_t msg, char *savefile)
rc = mu_mailbox_open (outbox, MU_STREAM_WRITE | MU_STREAM_CREAT);
if (rc)
mu_error (_("Cannot open output mailbox `%s': %s"),
filename, strerror (rc));
filename, mu_strerror (rc));
else
{
rc = mu_mailbox_append_message (outbox, msg);
if (rc)
mu_error (_("Cannot append message to `%s': %s"),
filename, strerror (rc));
filename, mu_strerror (rc));
}
mu_mailbox_close (outbox);
......