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
1 GNU mailutils NEWS -- history of user-visible changes. 2016-12-17 1 GNU mailutils NEWS -- history of user-visible changes. 2017-01-13
2 Copyright (C) 2002-2017 Free Software Foundation, Inc. 2 Copyright (C) 2002-2017 Free Software Foundation, Inc.
3 See the end of file for copying conditions. 3 See the end of file for copying conditions.
4 4
...@@ -7,6 +7,54 @@ Please send mailutils bug reports to <bug-mailutils@gnu.org>. ...@@ -7,6 +7,54 @@ Please send mailutils bug reports to <bug-mailutils@gnu.org>.
7 7
8 Version 3.1.90 (Git) 8 Version 3.1.90 (Git)
9 9
10 * mail
11
12 ** Modifying attachment name and filename
13
14 Two new options are provided for modifying attachment name (a.k.a
15 description), and file name:
16
17 --content-name=STRING
18 Sets the attachment name (description). Technically speaking, it
19 is the "name" parameter in the Content-Type MIME header.
20
21 --content-filename=NAME
22 Sets the file name (the "filename" parameter in the
23 Content-Description MIME header of the outgoing message.
24
25 Both options affect only the next `--attach' or `--attach-fd' option.
26
27 * Constructing attachments from command line
28
29 The new option `--attach-fd=N' instructs mail to read attachment from
30 file descriptor N. By default, the attachments created using this
31 option are unnamed, i.e. neither name parameter of the Content-Type
32 header, nor the filename parameter of the Content-Disposition header
33 are set. Use the --content-name and --content-filename options to
34 change these.
35
36 The option `--attach-fd=0' causes attachment to be read from the
37 standard input. The option `--attach=-' has the same effect. For
38 obvious reasons, the interactive mode is suppressed in this case.
39
40 The `--attach-fd' option is useful when calling `mail' from another
41 program.
42
43 Example:
44
45 Suppose that the 'mail' binary is opened at file descriptor 5 and
46 the mail.c file is opened at descriptor 6, the following command
47 line sends them as attachments:
48
49 mail --encoding=base64 \
50 --content-type=application/octet-stream \
51 --content-name="the mail(1) binary" --content-filename="mail" \
52 --attach-fd=5 \
53 --encoding=binary\
54 --content-type=text/plain --content-name="mail.c source file"\
55 --content-filename=mail.c --attach-fd=6 \
56 root@example.org
57
10 58
11 Version 3.1.1 - 2016-12-15 59 Version 3.1.1 - 2016-12-15
12 60
......
...@@ -2915,6 +2915,7 @@ Configuration Files}, for a detailed description of their format. ...@@ -2915,6 +2915,7 @@ Configuration Files}, for a detailed description of their format.
2915 * Invoking Mail:: Command Line Options. 2915 * Invoking Mail:: Command Line Options.
2916 * Specifying Messages:: How to Specify Message Sets. 2916 * Specifying Messages:: How to Specify Message Sets.
2917 * Composing Mail:: Composing Mail. 2917 * Composing Mail:: Composing Mail.
2918 * Attachments:: Attaching Files.
2918 * Reading Mail:: Reading Mail. 2919 * Reading Mail:: Reading Mail.
2919 * Scripting:: Scripting. 2920 * Scripting:: Scripting.
2920 * Mail Variables:: How to Alter the Behavior of @command{mail}. 2921 * Mail Variables:: How to Alter the Behavior of @command{mail}.
...@@ -2938,9 +2939,22 @@ mail sending mode, otherwise it operates in mail reading mode. ...@@ -2938,9 +2939,22 @@ mail sending mode, otherwise it operates in mail reading mode.
2938 @table @option 2939 @table @option
2939 @item -A @var{file} 2940 @item -A @var{file}
2940 @itemx --attach=@var{file} 2941 @itemx --attach=@var{file}
2941 Attach @var{file} to the composed message. The encoding and content 2942 Attach @var{file} to the composed message. The encoding, content
2942 type are controlled by the @option{--encoding} and 2943 type, and content description are controlled by the
2943 @option{--content-type} options, correspondingly. 2944 @option{--encoding}, @option{--content-type}, and
2945 @option{--content-name} options, correspondingly.
2946
2947 The option @option{--attach=-} instructs @command{mail} to read the
2948 file to be attached from the standard input. Interactive shell is
2949 disabled in this case.
2950
2951 @item --attach-fd=@var{fd}
2952 Read attachment body from the file descriptor @var{fd}. The
2953 descriptor must be open for reading. This option is useful when
2954 calling @command{mail} from another program.
2955
2956 See the options @option{--encoding}, @option{--content-type},
2957 @option{--content-name}, and @option{--content-filename}.
2944 2958
2945 @item -a @var{header}:@var{value} 2959 @item -a @var{header}:@var{value}
2946 @itemx --append=@var{header}:@var{value} 2960 @itemx --append=@var{header}:@var{value}
...@@ -2950,6 +2964,14 @@ Append the given header to the composed message. ...@@ -2950,6 +2964,14 @@ Append the given header to the composed message.
2950 This options sets the content type to be used by all subsequent 2964 This options sets the content type to be used by all subsequent
2951 @option{--attach} options. 2965 @option{--attach} options.
2952 2966
2967 @item --content-filename=@var{name}
2968 Set the @samp{filename} parameter in the @samp{Content-Disposition}
2969 header for the next @option{--attach-fd} option.
2970
2971 @item --content-name=@var{text}
2972 Set the @samp{name} parameter (description) in the @samp{Content-Type}
2973 header for the next @option{--attach} or @option{--attach-fd} option.
2974
2953 @item -E @var{command} 2975 @item -E @var{command}
2954 @itemx --exec=@var{command} 2976 @itemx --exec=@var{command}
2955 Execute @var{command} before opening the mailbox. Any number of 2977 Execute @var{command} before opening the mailbox. Any number of
...@@ -3412,6 +3434,115 @@ the old contents of your message. ...@@ -3412,6 +3434,115 @@ the old contents of your message.
3412 3434
3413 @c ********************************************************************* 3435 @c *********************************************************************
3414 3436
3437 @node Attachments
3438 @subsection Sending Attachments
3439
3440 The simplest way to attach a file from command line is by using the
3441 @option{--attach} (@option{-A}) option. Its argument specifies the
3442 file to attach. For example, the following will attach the content
3443 of the file @file{archive.tar}:
3444
3445 @example
3446 $ mail --attach=archive.tar
3447 @end example
3448
3449 By default, the content type will be set to
3450 @samp{application/octet-stream}, and the attachment will be encoded
3451 using the @samp{base64} encoding. To change the content type, use the
3452 @option{--content-type} option. For example, to send an HTML
3453 attachment:
3454
3455 @example
3456 $ mail --content-type=text/html --attach=in.html
3457 @end example
3458
3459 The @option{--content-type} option affects all @option{--attach}
3460 options that follow it. To change the content type, simply add
3461 another @option{--content-type} option. For example, to send both
3462 the HTML file and the archive:
3463
3464 @example
3465 $ mail --content-type=text/html --attach=in.html \
3466 --content-type=application/x-tar --attach=archive.tar
3467 @end example
3468
3469 Similarly, the encoding to use is set up by the @option{--encoding}
3470 option. As well as @option{--content-type}, this option affects all
3471 attachments supplied after it in the command line, until changed by
3472 the eventual next appearance of the same option. Extending the above
3473 example:
3474
3475 @example
3476 $ mail --content-type=text/html --encoding=quoted-printable \
3477 --attach=in.html \
3478 --content-type=application/x-tar --encoding=base64 \
3479 --attach=archive.tar
3480 @end example
3481
3482 Each attachment can also be assigned a @dfn{description} and a
3483 @dfn{file name}. Normally, these are the same as the file name
3484 supplied with the @option{--attach} option. However, you can change
3485 either or both of them using the @option{--content-name} and
3486 @option{--content-filename}, correspondingly. Both of these options
3487 affect only the next @option{--attach} (or @option{--attach-fd}, see
3488 below) option.
3489
3490 All the examples above will enter the usual interactive shell,
3491 allowing you to compose the body of the message. If that's not
3492 needed, the non-interactive use can be forced by redirecting
3493 @file{/dev/null} to the standard input, e.g.:
3494
3495 @example
3496 $ mail --attach=archive.tar < /dev/null
3497 @end example
3498
3499 This will normally produce a message saying:
3500
3501 @example
3502 mail: Null message body; hope that's ok
3503 @end example
3504
3505 To suppress this message, unset the @samp{nullbodymsg} variable,
3506 as shown in the example below:
3507
3508 @example
3509 $ mail -E 'set nonullbodymsg' --attach=archive.tar < /dev/null
3510 @end example
3511
3512 The option @option{--attach=-} forces @command{mail} to read the file
3513 to be attached from the standard input stream. This option implies
3514 disables the interactive mode and sets @samp{nonullbodymsg}
3515 implicitly, so that the above example can be rewritten as:
3516
3517 @example
3518 $ mail --attach=- < archive.tar
3519 @end example
3520
3521 Special option is provided to facilitate the use of @command{mail}
3522 in scripts. The @option{--attach-fd=@var{N}} instructs the program to
3523 read the data to be attached from the file descriptor @var{N}. The
3524 above example is equivalent to:
3525
3526 @example
3527 $ mail --attach-fd=0 < archive.tar
3528 @end example
3529
3530 Attachments created using this option have neither filename not
3531 description set, so normally the use of @option{--content-name} and/or
3532 @option{--content-filename} is advised.
3533
3534 @example
3535 $ mail --subject 'mail(1)' \
3536 --content-name="The mail(1) binary" --content-filename="mail" \
3537 --attach-fd 5 \
3538 --encoding=binary --content-type=text/plain \
3539 --content-name="mail.c source file" --content-filename=mail.c \
3540 --attach-fd 6 gray@@example.org 5</usr/bin/mail \
3541 6<mailutils/mail/mail.c
3542 @end example
3543
3544 @c *********************************************************************
3545
3415 @node Reading Mail 3546 @node Reading Mail
3416 @subsection Reading Mail 3547 @subsection Reading Mail
3417 3548
......
...@@ -35,6 +35,11 @@ const char *program_version = "mail (" PACKAGE_STRING ")"; ...@@ -35,6 +35,11 @@ const char *program_version = "mail (" PACKAGE_STRING ")";
35 int hint; 35 int hint;
36 char *file; 36 char *file;
37 char *user; 37 char *user;
38
39 char *default_encoding;
40 char *default_content_type;
41 char *content_name;
42 char *content_filename;
38 43
39 static void 44 static void
40 cli_f_option (struct mu_parseopt *po, struct mu_option *opt, char const *arg) 45 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) ...@@ -134,9 +139,45 @@ cli_append (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
134 static void 139 static void
135 cli_attach (struct mu_parseopt *po, struct mu_option *opt, char const *arg) 140 cli_attach (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
136 { 141 {
142 int fd = -1;
143
137 hint |= HINT_SEND_MODE; 144 hint |= HINT_SEND_MODE;
138 if (send_attach_file_default (arg)) 145 if (strcmp (arg, "-") == 0)
146 {
147 arg = NULL;
148 fd = 0;
149 }
150 if (send_attach_file (fd, arg, content_filename, content_name,
151 default_content_type, default_encoding))
139 exit (1); 152 exit (1);
153
154 free (content_name);
155 content_name = NULL;
156 free (content_filename);
157 content_filename = NULL;
158 }
159
160 static void
161 cli_attach_fd (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
162 {
163 int rc, fd;
164
165 hint |= HINT_SEND_MODE;
166 rc = mu_str_to_c (arg, mu_c_int, &fd, NULL);
167 if (rc)
168 {
169 mu_parseopt_error (po, _("%s: bad descriptor"), arg);
170 exit (po->po_exit_error);
171 }
172
173 if (send_attach_file (fd, NULL, content_filename, content_name,
174 default_content_type, default_encoding))
175 exit (1);
176
177 free (content_name);
178 content_name = NULL;
179 free (content_filename);
180 content_filename = NULL;
140 } 181 }
141 182
142 static struct mu_option mail_options[] = { 183 static struct mu_option mail_options[] = {
...@@ -212,10 +253,21 @@ static struct mu_option mail_options[] = { ...@@ -212,10 +253,21 @@ static struct mu_option mail_options[] = {
212 N_("set content type for subsequent --attach options"), 253 N_("set content type for subsequent --attach options"),
213 mu_c_string, &default_content_type }, 254 mu_c_string, &default_content_type },
214 255
256 { "content-name", 0, N_("NAME"), MU_OPTION_DEFAULT,
257 N_("set the Content-Type name parameter for the next --attach option"),
258 mu_c_string, &content_name },
259 { "content-filename", 0, N_("NAME"), MU_OPTION_DEFAULT,
260 N_("set the Content-Disposition filename parameter for the next --attach option"),
261 mu_c_string, &content_filename },
262
215 { "attach", 'A', N_("FILE"), MU_OPTION_DEFAULT, 263 { "attach", 'A', N_("FILE"), MU_OPTION_DEFAULT,
216 N_("attach FILE"), 264 N_("attach FILE"),
217 mu_c_string, NULL, cli_attach }, 265 mu_c_string, NULL, cli_attach },
218 266
267 { "attach-fd", 0, N_("FD"), MU_OPTION_DEFAULT,
268 N_("attach from file descriptor FD"),
269 mu_c_string, NULL, cli_attach_fd },
270
219 MU_OPTION_END 271 MU_OPTION_END
220 }, *options[] = { mail_options, NULL }; 272 }, *options[] = { mail_options, NULL };
221 273
......
...@@ -258,7 +258,11 @@ extern char *mail_expand_name (const char *name); ...@@ -258,7 +258,11 @@ extern char *mail_expand_name (const char *name);
258 258
259 extern void send_append_header (char const *text); 259 extern void send_append_header (char const *text);
260 extern void send_append_header2 (char const *name, char const *value, int mode); 260 extern void send_append_header2 (char const *name, char const *value, int mode);
261 extern int send_attach_file_default (const char *name); 261 extern int send_attach_file (int fd,
262 const char *filename,
263 const char *content_filename,
264 const char *content_name,
265 const char *content_type, const char *encoding);
262 266
263 extern int escape_check_args (int argc, char **argv, int minargs, int maxargs); 267 extern int escape_check_args (int argc, char **argv, int minargs, int maxargs);
264 268
......
...@@ -134,55 +134,97 @@ mail_sendheader (int argc, char **argv) ...@@ -134,55 +134,97 @@ mail_sendheader (int argc, char **argv)
134 /* Attachments */ 134 /* Attachments */
135 struct atchinfo 135 struct atchinfo
136 { 136 {
137 char *id;
137 char *encoding; 138 char *encoding;
138 char *content_type; 139 char *content_type;
140 char *name;
139 char *filename; 141 char *filename;
142 mu_stream_t source;
140 }; 143 };
141 144
142 static mu_list_t attlist; 145 static mu_list_t attlist;
143 146
144 char *default_encoding;
145 char *default_content_type;
146
147 static void 147 static void
148 atchinfo_free (void *p) 148 atchinfo_free (void *p)
149 { 149 {
150 struct atchinfo *ap = p; 150 struct atchinfo *ap = p;
151 free (ap->id);
151 free (ap->encoding); 152 free (ap->encoding);
152 free (ap->content_type); 153 free (ap->content_type);
154 free (ap->name);
153 free (ap->filename); 155 free (ap->filename);
156 mu_stream_destroy (&ap->source);
154 free (ap); 157 free (ap);
155 } 158 }
156 159
157 int 160 int
158 send_attach_file (const char *name, 161 send_attach_file (int fd,
162 const char *realname,
163 const char *content_filename, const char *content_name,
159 const char *content_type, const char *encoding) 164 const char *content_type, const char *encoding)
160 { 165 {
161 int rc; 166 int rc;
162 struct stat st; 167 struct stat st;
163 struct atchinfo *aptr; 168 struct atchinfo *aptr;
164 mu_list_t list; 169 mu_list_t list;
165 170 mu_stream_t stream = NULL;
166 if (stat (name, &st)) 171 char *id = NULL;
172
173 if (fd >= 0)
167 { 174 {
168 if (errno == ENOENT) 175 rc = mu_fd_stream_create (&stream, NULL, fd, MU_STREAM_READ);
176 if (rc)
169 { 177 {
170 mu_error (_("%s: file does not exist"), name); 178 mu_error (_("can't open descriptor %d: %s"), fd, mu_strerror (rc));
171 return 1; 179 return 1;
172 } 180 }
173 else 181 mu_asprintf (&id, "fd %d", fd);
182 if (fd == 0)
174 { 183 {
175 mu_error (_("%s: cannot stat: %s"), name, mu_strerror (errno)); 184 mu_stream_destroy (&mu_strin);
176 return 1; 185 mu_nullstream_create (&mu_strin, MU_STREAM_READ);
186 mu_stream_ioctl (mu_strin, MU_IOCTL_NULLSTREAM,
187 MU_IOCTL_NULLSTREAM_SET_PATTERN, NULL);
188 util_do_command ("set nullbody nullbodymsg");
177 } 189 }
178 } 190 }
179 191 else if (realname)
180 if (!S_ISREG (st.st_mode))
181 { 192 {
182 mu_error (_("%s: not a regular file"), name); 193 if (!content_filename)
183 return 1; 194 content_filename = realname;
184 } 195 if (stat (realname, &st))
196 {
197 if (errno == ENOENT)
198 {
199 mu_error (_("%s: file does not exist"), realname);
200 return 1;
201 }
202 else
203 {
204 mu_error (_("%s: cannot stat: %s"), realname,
205 mu_strerror (errno));
206 return 1;
207 }
208 }
209
210 if (!S_ISREG (st.st_mode))
211 {
212 mu_error (_("%s: not a regular file"), realname);
213 return 1;
214 }
185 215
216 rc = mu_mapfile_stream_create (&stream, realname, MU_STREAM_READ);
217 if (rc)
218 {
219 mu_error (_("can't open file %s: %s"),
220 realname, mu_strerror (rc));
221 return 1;
222 }
223 mu_asprintf (&id, "\"%s\"", realname);
224 }
225 else
226 abort ();
227
186 if (!encoding) 228 if (!encoding)
187 encoding = "base64"; 229 encoding = "base64";
188 mu_filter_get_list (&list); 230 mu_filter_get_list (&list);
...@@ -190,6 +232,8 @@ send_attach_file (const char *name, ...@@ -190,6 +232,8 @@ send_attach_file (const char *name,
190 if (rc) 232 if (rc)
191 { 233 {
192 mu_error (_("unsupported encoding: %s"), encoding); 234 mu_error (_("unsupported encoding: %s"), encoding);
235 free (id);
236 mu_stream_destroy (&stream);
193 return 1; 237 return 1;
194 } 238 }
195 239
...@@ -205,11 +249,13 @@ send_attach_file (const char *name, ...@@ -205,11 +249,13 @@ send_attach_file (const char *name,
205 } 249 }
206 aptr = mu_alloc (sizeof (*aptr)); 250 aptr = mu_alloc (sizeof (*aptr));
207 251
252 aptr->id = id;
208 aptr->encoding = mu_strdup (encoding); 253 aptr->encoding = mu_strdup (encoding);
209 aptr->content_type = mu_strdup (content_type ? 254 aptr->content_type = mu_strdup (content_type ?
210 content_type : 255 content_type : "application/octet-stream");
211 "application/octet-stream"); 256 aptr->name = content_name ? mu_strdup (content_name) : NULL;
212 aptr->filename = mu_strdup (name); 257 aptr->filename = content_filename ? mu_strdup (content_filename) : NULL;
258 aptr->source = stream;
213 rc = mu_list_append (attlist, aptr); 259 rc = mu_list_append (attlist, aptr);
214 if (rc) 260 if (rc)
215 { 261 {
...@@ -220,12 +266,6 @@ send_attach_file (const char *name, ...@@ -220,12 +266,6 @@ send_attach_file (const char *name,
220 } 266 }
221 267
222 int 268 int
223 send_attach_file_default (const char *name)
224 {
225 return send_attach_file (name, default_content_type, default_encoding);
226 }
227
228 int
229 escape_list_attachments (int argc, char **argv, compose_env_t *env) 269 escape_list_attachments (int argc, char **argv, compose_env_t *env)
230 { 270 {
231 mu_iterator_t itr; 271 mu_iterator_t itr;
...@@ -246,7 +286,7 @@ escape_list_attachments (int argc, char **argv, compose_env_t *env) ...@@ -246,7 +286,7 @@ escape_list_attachments (int argc, char **argv, compose_env_t *env)
246 continue; 286 continue;
247 287
248 mu_printf ("%3d %-12s %-30s %-s\n", 288 mu_printf ("%3d %-12s %-30s %-s\n",
249 i, aptr->filename, aptr->content_type, aptr->encoding); 289 i, aptr->id, aptr->content_type, aptr->encoding);
250 } 290 }
251 mu_iterator_destroy (&itr); 291 mu_iterator_destroy (&itr);
252 292
...@@ -266,7 +306,8 @@ escape_attach (int argc, char **argv, compose_env_t *env) ...@@ -266,7 +306,8 @@ escape_attach (int argc, char **argv, compose_env_t *env)
266 case 3: 306 case 3:
267 content_type = argv[2]; 307 content_type = argv[2];
268 case 2: 308 case 2:
269 return send_attach_file (argv[1], content_type, encoding); 309 return send_attach_file (-1, argv[1], argv[1], argv[1],
310 content_type, encoding);
270 default: 311 default:
271 return escape_check_args (argc, argv, 2, 4); 312 return escape_check_args (argc, argv, 2, 4);
272 } 313 }
...@@ -309,12 +350,20 @@ saveatt (void *item, void *data) ...@@ -309,12 +350,20 @@ saveatt (void *item, void *data)
309 int rc; 350 int rc;
310 size_t nparts; 351 size_t nparts;
311 char *p; 352 char *p;
312 353
313 rc = mu_message_create_attachment (aptr->content_type, aptr->encoding, 354 rc = mu_attachment_create (&part, aptr->content_type, aptr->encoding,
314 aptr->filename, &part); 355 aptr->name, aptr->filename);
356 if (rc)
357 {
358 mu_error (_("can't create attachment %s: %s"),
359 aptr->id, mu_strerror (rc));
360 return 1;
361 }
362
363 rc = mu_attachment_copy_from_stream (part, aptr->source, aptr->encoding);
315 if (rc) 364 if (rc)
316 { 365 {
317 mu_error (_("cannot attach \"%s\": %s"), aptr->filename, 366 mu_error (_("cannot attach %s: %s"), aptr->filename,
318 mu_strerror (rc)); 367 mu_strerror (rc));
319 return 1; 368 return 1;
320 } 369 }
......