Commit eabddf8f eabddf8f5f18afddc13281a5fbaa562f75e76b5f by Sergey Poznyakoff

mail: new options to read attachments from file descriptors

* mail/mail.c (default_encoding,default_content_type)
(content_name,content_filename): New statics.
(cli_attach): Use the value of --content-name option as the
content-type name parameter.
The "-" argument instructs the program to read attachment from
stdin (eqv --attach-fd=0)
(cli_attach_fd): New function.
(mail_options): New options: --content-name, --content-filename,
--attach-fd
* mail/mail.h (send_attach_file_default): Remove.
(send_attach_file): New proto.
* mail/send.c (atchinfo): New fields: id, name, source.
(atchinfo_free): Update.
(default_encoding,default_content_type): Remove globals.
(send_attach_file): Rewrite.
(send_attach_file_default): Remove.
(escape_list_attachments): Print attachment identifier instead
of the file name, which can be NULL.
(saveatt): Use mu_attachment_create and mu_attachment_copy_from_stream.

* NEWS: Update.
* doc/texinfo/programs.texi: Document the new options.
1 parent 0c76b2b1
GNU mailutils NEWS -- history of user-visible changes. 2016-12-17
GNU mailutils NEWS -- history of user-visible changes. 2017-01-13
Copyright (C) 2002-2017 Free Software Foundation, Inc.
See the end of file for copying conditions.
......@@ -7,6 +7,54 @@ Please send mailutils bug reports to <bug-mailutils@gnu.org>.
Version 3.1.90 (Git)
* mail
** Modifying attachment name and filename
Two new options are provided for modifying attachment name (a.k.a
description), and file name:
--content-name=STRING
Sets the attachment name (description). Technically speaking, it
is the "name" parameter in the Content-Type MIME header.
--content-filename=NAME
Sets the file name (the "filename" parameter in the
Content-Description MIME header of the outgoing message.
Both options affect only the next `--attach' or `--attach-fd' option.
* Constructing attachments from command line
The new option `--attach-fd=N' instructs mail to read attachment from
file descriptor N. By default, the attachments created using this
option are unnamed, i.e. neither name parameter of the Content-Type
header, nor the filename parameter of the Content-Disposition header
are set. Use the --content-name and --content-filename options to
change these.
The option `--attach-fd=0' causes attachment to be read from the
standard input. The option `--attach=-' has the same effect. For
obvious reasons, the interactive mode is suppressed in this case.
The `--attach-fd' option is useful when calling `mail' from another
program.
Example:
Suppose that the 'mail' binary is opened at file descriptor 5 and
the mail.c file is opened at descriptor 6, the following command
line sends them as attachments:
mail --encoding=base64 \
--content-type=application/octet-stream \
--content-name="the mail(1) binary" --content-filename="mail" \
--attach-fd=5 \
--encoding=binary\
--content-type=text/plain --content-name="mail.c source file"\
--content-filename=mail.c --attach-fd=6 \
root@example.org
Version 3.1.1 - 2016-12-15
......
......@@ -2915,6 +2915,7 @@ Configuration Files}, for a detailed description of their format.
* Invoking Mail:: Command Line Options.
* Specifying Messages:: How to Specify Message Sets.
* Composing Mail:: Composing Mail.
* Attachments:: Attaching Files.
* Reading Mail:: Reading Mail.
* Scripting:: Scripting.
* Mail Variables:: How to Alter the Behavior of @command{mail}.
......@@ -2938,9 +2939,22 @@ mail sending mode, otherwise it operates in mail reading mode.
@table @option
@item -A @var{file}
@itemx --attach=@var{file}
Attach @var{file} to the composed message. The encoding and content
type are controlled by the @option{--encoding} and
@option{--content-type} options, correspondingly.
Attach @var{file} to the composed message. The encoding, content
type, and content description are controlled by the
@option{--encoding}, @option{--content-type}, and
@option{--content-name} options, correspondingly.
The option @option{--attach=-} instructs @command{mail} to read the
file to be attached from the standard input. Interactive shell is
disabled in this case.
@item --attach-fd=@var{fd}
Read attachment body from the file descriptor @var{fd}. The
descriptor must be open for reading. This option is useful when
calling @command{mail} from another program.
See the options @option{--encoding}, @option{--content-type},
@option{--content-name}, and @option{--content-filename}.
@item -a @var{header}:@var{value}
@itemx --append=@var{header}:@var{value}
......@@ -2950,6 +2964,14 @@ Append the given header to the composed message.
This options sets the content type to be used by all subsequent
@option{--attach} options.
@item --content-filename=@var{name}
Set the @samp{filename} parameter in the @samp{Content-Disposition}
header for the next @option{--attach-fd} option.
@item --content-name=@var{text}
Set the @samp{name} parameter (description) in the @samp{Content-Type}
header for the next @option{--attach} or @option{--attach-fd} option.
@item -E @var{command}
@itemx --exec=@var{command}
Execute @var{command} before opening the mailbox. Any number of
......@@ -3412,6 +3434,115 @@ the old contents of your message.
@c *********************************************************************
@node Attachments
@subsection Sending Attachments
The simplest way to attach a file from command line is by using the
@option{--attach} (@option{-A}) option. Its argument specifies the
file to attach. For example, the following will attach the content
of the file @file{archive.tar}:
@example
$ mail --attach=archive.tar
@end example
By default, the content type will be set to
@samp{application/octet-stream}, and the attachment will be encoded
using the @samp{base64} encoding. To change the content type, use the
@option{--content-type} option. For example, to send an HTML
attachment:
@example
$ mail --content-type=text/html --attach=in.html
@end example
The @option{--content-type} option affects all @option{--attach}
options that follow it. To change the content type, simply add
another @option{--content-type} option. For example, to send both
the HTML file and the archive:
@example
$ mail --content-type=text/html --attach=in.html \
--content-type=application/x-tar --attach=archive.tar
@end example
Similarly, the encoding to use is set up by the @option{--encoding}
option. As well as @option{--content-type}, this option affects all
attachments supplied after it in the command line, until changed by
the eventual next appearance of the same option. Extending the above
example:
@example
$ mail --content-type=text/html --encoding=quoted-printable \
--attach=in.html \
--content-type=application/x-tar --encoding=base64 \
--attach=archive.tar
@end example
Each attachment can also be assigned a @dfn{description} and a
@dfn{file name}. Normally, these are the same as the file name
supplied with the @option{--attach} option. However, you can change
either or both of them using the @option{--content-name} and
@option{--content-filename}, correspondingly. Both of these options
affect only the next @option{--attach} (or @option{--attach-fd}, see
below) option.
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
@file{/dev/null} to the standard input, e.g.:
@example
$ mail --attach=archive.tar < /dev/null
@end example
This will normally produce a message saying:
@example
mail: Null message body; hope that's ok
@end example
To suppress this message, unset the @samp{nullbodymsg} variable,
as shown in the example below:
@example
$ mail -E 'set nonullbodymsg' --attach=archive.tar < /dev/null
@end example
The option @option{--attach=-} forces @command{mail} to read the file
to be attached from the standard input stream. This option implies
disables the interactive mode and sets @samp{nonullbodymsg}
implicitly, so that the above example can be rewritten as:
@example
$ mail --attach=- < archive.tar
@end example
Special option is provided to facilitate the use of @command{mail}
in scripts. The @option{--attach-fd=@var{N}} instructs the program to
read the data to be attached from the file descriptor @var{N}. The
above example is equivalent to:
@example
$ mail --attach-fd=0 < archive.tar
@end example
Attachments created using this option have neither filename not
description set, so normally the use of @option{--content-name} and/or
@option{--content-filename} is advised.
@example
$ mail --subject 'mail(1)' \
--content-name="The mail(1) binary" --content-filename="mail" \
--attach-fd 5 \
--encoding=binary --content-type=text/plain \
--content-name="mail.c source file" --content-filename=mail.c \
--attach-fd 6 gray@@example.org 5</usr/bin/mail \
6<mailutils/mail/mail.c
@end example
@c *********************************************************************
@node Reading Mail
@subsection Reading Mail
......
......@@ -35,6 +35,11 @@ const char *program_version = "mail (" PACKAGE_STRING ")";
int hint;
char *file;
char *user;
char *default_encoding;
char *default_content_type;
char *content_name;
char *content_filename;
static void
cli_f_option (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
......@@ -134,9 +139,45 @@ cli_append (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
static void
cli_attach (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
int fd = -1;
hint |= HINT_SEND_MODE;
if (send_attach_file_default (arg))
if (strcmp (arg, "-") == 0)
{
arg = NULL;
fd = 0;
}
if (send_attach_file (fd, arg, content_filename, content_name,
default_content_type, default_encoding))
exit (1);
free (content_name);
content_name = NULL;
free (content_filename);
content_filename = NULL;
}
static void
cli_attach_fd (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
int rc, fd;
hint |= HINT_SEND_MODE;
rc = mu_str_to_c (arg, mu_c_int, &fd, NULL);
if (rc)
{
mu_parseopt_error (po, _("%s: bad descriptor"), arg);
exit (po->po_exit_error);
}
if (send_attach_file (fd, NULL, content_filename, content_name,
default_content_type, default_encoding))
exit (1);
free (content_name);
content_name = NULL;
free (content_filename);
content_filename = NULL;
}
static struct mu_option mail_options[] = {
......@@ -212,10 +253,21 @@ static struct mu_option mail_options[] = {
N_("set content type for subsequent --attach options"),
mu_c_string, &default_content_type },
{ "content-name", 0, N_("NAME"), MU_OPTION_DEFAULT,
N_("set the Content-Type name parameter for the next --attach option"),
mu_c_string, &content_name },
{ "content-filename", 0, N_("NAME"), MU_OPTION_DEFAULT,
N_("set the Content-Disposition filename parameter for the next --attach option"),
mu_c_string, &content_filename },
{ "attach", 'A', N_("FILE"), MU_OPTION_DEFAULT,
N_("attach FILE"),
mu_c_string, NULL, cli_attach },
{ "attach-fd", 0, N_("FD"), MU_OPTION_DEFAULT,
N_("attach from file descriptor FD"),
mu_c_string, NULL, cli_attach_fd },
MU_OPTION_END
}, *options[] = { mail_options, NULL };
......
......@@ -258,7 +258,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_default (const char *name);
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 int escape_check_args (int argc, char **argv, int minargs, int maxargs);
......
......@@ -134,55 +134,97 @@ mail_sendheader (int argc, char **argv)
/* Attachments */
struct atchinfo
{
char *id;
char *encoding;
char *content_type;
char *name;
char *filename;
mu_stream_t source;
};
static mu_list_t attlist;
char *default_encoding;
char *default_content_type;
static void
atchinfo_free (void *p)
{
struct atchinfo *ap = p;
free (ap->id);
free (ap->encoding);
free (ap->content_type);
free (ap->name);
free (ap->filename);
mu_stream_destroy (&ap->source);
free (ap);
}
int
send_attach_file (const char *name,
send_attach_file (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;
if (stat (name, &st))
mu_stream_t stream = NULL;
char *id = NULL;
if (fd >= 0)
{
if (errno == ENOENT)
rc = mu_fd_stream_create (&stream, NULL, fd, MU_STREAM_READ);
if (rc)
{
mu_error (_("%s: file does not exist"), name);
mu_error (_("can't open descriptor %d: %s"), fd, mu_strerror (rc));
return 1;
}
else
mu_asprintf (&id, "fd %d", fd);
if (fd == 0)
{
mu_error (_("%s: cannot stat: %s"), name, mu_strerror (errno));
return 1;
mu_stream_destroy (&mu_strin);
mu_nullstream_create (&mu_strin, MU_STREAM_READ);
mu_stream_ioctl (mu_strin, MU_IOCTL_NULLSTREAM,
MU_IOCTL_NULLSTREAM_SET_PATTERN, NULL);
util_do_command ("set nullbody nullbodymsg");
}
}
if (!S_ISREG (st.st_mode))
else if (realname)
{
mu_error (_("%s: not a regular file"), name);
return 1;
}
if (!content_filename)
content_filename = realname;
if (stat (realname, &st))
{
if (errno == ENOENT)
{
mu_error (_("%s: file does not exist"), realname);
return 1;
}
else
{
mu_error (_("%s: cannot stat: %s"), realname,
mu_strerror (errno));
return 1;
}
}
if (!S_ISREG (st.st_mode))
{
mu_error (_("%s: not a regular file"), realname);
return 1;
}
rc = mu_mapfile_stream_create (&stream, realname, MU_STREAM_READ);
if (rc)
{
mu_error (_("can't open file %s: %s"),
realname, mu_strerror (rc));
return 1;
}
mu_asprintf (&id, "\"%s\"", realname);
}
else
abort ();
if (!encoding)
encoding = "base64";
mu_filter_get_list (&list);
......@@ -190,6 +232,8 @@ send_attach_file (const char *name,
if (rc)
{
mu_error (_("unsupported encoding: %s"), encoding);
free (id);
mu_stream_destroy (&stream);
return 1;
}
......@@ -205,11 +249,13 @@ send_attach_file (const char *name,
}
aptr = mu_alloc (sizeof (*aptr));
aptr->id = id;
aptr->encoding = mu_strdup (encoding);
aptr->content_type = mu_strdup (content_type ?
content_type :
"application/octet-stream");
aptr->filename = mu_strdup (name);
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;
rc = mu_list_append (attlist, aptr);
if (rc)
{
......@@ -220,12 +266,6 @@ send_attach_file (const char *name,
}
int
send_attach_file_default (const char *name)
{
return send_attach_file (name, default_content_type, default_encoding);
}
int
escape_list_attachments (int argc, char **argv, compose_env_t *env)
{
mu_iterator_t itr;
......@@ -246,7 +286,7 @@ escape_list_attachments (int argc, char **argv, compose_env_t *env)
continue;
mu_printf ("%3d %-12s %-30s %-s\n",
i, aptr->filename, aptr->content_type, aptr->encoding);
i, aptr->id, aptr->content_type, aptr->encoding);
}
mu_iterator_destroy (&itr);
......@@ -266,7 +306,8 @@ escape_attach (int argc, char **argv, compose_env_t *env)
case 3:
content_type = argv[2];
case 2:
return send_attach_file (argv[1], content_type, encoding);
return send_attach_file (-1, argv[1], argv[1], argv[1],
content_type, encoding);
default:
return escape_check_args (argc, argv, 2, 4);
}
......@@ -309,12 +350,20 @@ saveatt (void *item, void *data)
int rc;
size_t nparts;
char *p;
rc = mu_message_create_attachment (aptr->content_type, aptr->encoding,
aptr->filename, &part);
rc = mu_attachment_create (&part, aptr->content_type, aptr->encoding,
aptr->name, aptr->filename);
if (rc)
{
mu_error (_("can't create attachment %s: %s"),
aptr->id, mu_strerror (rc));
return 1;
}
rc = mu_attachment_copy_from_stream (part, aptr->source, aptr->encoding);
if (rc)
{
mu_error (_("cannot attach \"%s\": %s"), aptr->filename,
mu_error (_("cannot attach %s: %s"), aptr->filename,
mu_strerror (rc));
return 1;
}
......