3 * Routines for managing attachments
6 * Copyright (C) 1996-2000,2002,2007,2010 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 1999-2006 Thomas Roessler <roessler@does-not-exist.org>
10 * This program is free software: you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License as published by the Free Software
12 * Foundation, either version 2 of the License, or (at your option) any later
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
20 * You should have received a copy of the GNU General Public License along with
21 * this program. If not, see <http://www.gnu.org/licenses/>.
25 * @page recvattach Routines for managing attachments
27 * Routines for managing attachments
36 #include "mutt/mutt.h"
37 #include "config/lib.h"
38 #include "email/lib.h"
41 #include "recvattach.h"
46 #include "format_flags.h"
53 #include "mutt_attach.h"
54 #include "mutt_menu.h"
55 #include "mutt_parse.h"
56 #include "mutt_window.h"
59 #include "ncrypt/ncrypt.h"
70 /* These Config Variables are only used in recvattach.c */
71 char *C_AttachSaveDir; ///< Config: Default directory where attachments are saved
72 char *C_AttachSaveWithoutPrompting; ///< Config: If true, then don't prompt to save
73 char *C_AttachSep; ///< Config: Separator to add between saved/printed/piped attachments
74 bool C_AttachSplit; ///< Config: Save/print/pipe tagged messages individually
75 bool C_DigestCollapse; ///< Config: Hide the subparts of a multipart/digest
76 char *C_MessageFormat; ///< Config: printf-like format string for listing attached messages
78 static void mutt_update_recvattach_menu(struct AttachCtx *actx, struct Menu *menu, bool init);
80 static const char *Mailbox_is_read_only = N_("Mailbox is read-only");
82 #define CHECK_READONLY \
83 if (!Context || !Context->mailbox || Context->mailbox->readonly) \
86 mutt_error(_(Mailbox_is_read_only)); \
90 #define CUR_ATTACH actx->idx[actx->v2r[menu->current]]
92 static const struct Mapping AttachHelp[] = {
93 { N_("Exit"), OP_EXIT }, { N_("Save"), OP_SAVE }, { N_("Pipe"), OP_PIPE },
94 { N_("Print"), OP_PRINT }, { N_("Help"), OP_HELP }, { NULL, 0 },
97 static const char *Function_not_permitted =
98 N_("Function not permitted in attach-message mode");
100 #define CHECK_ATTACH \
104 mutt_error(_(Function_not_permitted)); \
109 * mutt_update_v2r - Update the virtual list of attachments
110 * @param actx Attachment context
112 * Update the record of the number of attachments and the status of the tree.
114 static void mutt_update_v2r(struct AttachCtx *actx)
116 int vindex, rindex, curlevel;
121 while (rindex < actx->idxlen)
123 actx->v2r[vindex++] = rindex;
124 if (actx->idx[rindex]->content->collapsed)
126 curlevel = actx->idx[rindex]->level;
130 } while ((rindex < actx->idxlen) && (actx->idx[rindex]->level > curlevel));
136 actx->vcount = vindex;
140 * mutt_update_tree - Refresh the list of attachments
141 * @param actx Attachment context
143 void mutt_update_tree(struct AttachCtx *actx)
148 mutt_update_v2r(actx);
150 for (int vindex = 0; vindex < actx->vcount; vindex++)
152 const int rindex = actx->v2r[vindex];
153 actx->idx[rindex]->num = vindex;
154 if ((2 * (actx->idx[rindex]->level + 2)) < sizeof(buf))
156 if (actx->idx[rindex]->level)
158 s = buf + 2 * (actx->idx[rindex]->level - 1);
159 *s++ = (actx->idx[rindex]->content->next) ? MUTT_TREE_LTEE : MUTT_TREE_LLCORNER;
160 *s++ = MUTT_TREE_HLINE;
161 *s++ = MUTT_TREE_RARROW;
168 if (actx->idx[rindex]->tree)
170 if (mutt_str_strcmp(actx->idx[rindex]->tree, buf) != 0)
171 mutt_str_replace(&actx->idx[rindex]->tree, buf);
174 actx->idx[rindex]->tree = mutt_str_strdup(buf);
176 if (((2 * (actx->idx[rindex]->level + 2)) < sizeof(buf)) &&
177 actx->idx[rindex]->level)
179 s = buf + 2 * (actx->idx[rindex]->level - 1);
180 *s++ = (actx->idx[rindex]->content->next) ? '\005' : '\006';
187 * attach_format_str - Format a string for the attachment menu - Implements ::format_t
189 * | Expando | Description
190 * |:--------|:--------------------------------------------------------
191 * | \%C | Character set
192 * | \%c | Character set: convert?
193 * | \%D | Deleted flag
194 * | \%d | Description
195 * | \%e | MIME content-transfer-encoding
197 * | \%F | Filename for content-disposition header
198 * | \%I | Content-disposition, either I (inline) or A (attachment)
199 * | \%m | Major MIME type
200 * | \%M | MIME subtype
201 * | \%n | Attachment number
202 * | \%Q | 'Q', if MIME part qualifies for attachment counting
204 * | \%t | Tagged flag
207 * | \%X | Number of qualifying MIME parts in this part and its children
209 const char *attach_format_str(char *buf, size_t buflen, size_t col, int cols,
210 char op, const char *src, const char *prec,
211 const char *if_str, const char *else_str,
212 unsigned long data, MuttFormatFlags flags)
216 struct AttachPtr *aptr = (struct AttachPtr *) data;
217 bool optional = (flags & MUTT_FORMAT_OPTIONAL);
224 if (mutt_is_text_part(aptr->content) &&
225 mutt_body_get_charset(aptr->content, charset, sizeof(charset)))
227 mutt_format_s(buf, buflen, prec, charset);
230 mutt_format_s(buf, buflen, prec, "");
232 else if (!mutt_is_text_part(aptr->content) ||
233 !mutt_body_get_charset(aptr->content, charset, sizeof(charset)))
242 snprintf(fmt, sizeof(fmt), "%%%sc", prec);
243 snprintf(buf, buflen, fmt,
244 ((aptr->content->type != TYPE_TEXT) || aptr->content->noconv) ? 'n' : 'c');
246 else if ((aptr->content->type != TYPE_TEXT) || aptr->content->noconv)
252 if (aptr->content->description)
254 mutt_format_s(buf, buflen, prec, aptr->content->description);
257 if (mutt_is_message_type(aptr->content->type, aptr->content->subtype) &&
258 C_MessageFormat && aptr->content->email)
261 mutt_make_string_flags(s, sizeof(s), cols, C_MessageFormat, NULL,
262 NULL, aptr->content->email,
263 MUTT_FORMAT_FORCESUBJ | MUTT_FORMAT_ARROWCURSOR);
266 mutt_format_s(buf, buflen, prec, s);
270 if (!aptr->content->d_filename && !aptr->content->filename)
272 mutt_format_s(buf, buflen, prec, "<no description>");
276 else if (aptr->content->description ||
277 (mutt_is_message_type(aptr->content->type, aptr->content->subtype) &&
278 C_MessageFormat && aptr->content->email))
286 if (aptr->content->d_filename)
288 mutt_format_s(buf, buflen, prec, aptr->content->d_filename);
292 else if (!aptr->content->d_filename && !aptr->content->filename)
301 if (aptr->content->filename && (*aptr->content->filename == '/'))
303 struct Buffer *path = mutt_buffer_pool_get();
305 mutt_buffer_strcpy(path, aptr->content->filename);
306 mutt_buffer_pretty_mailbox(path);
307 mutt_format_s(buf, buflen, prec, mutt_b2s(path));
308 mutt_buffer_pool_release(&path);
311 mutt_format_s(buf, buflen, prec, NONULL(aptr->content->filename));
313 else if (!aptr->content->filename)
318 snprintf(buf, buflen, "%c", aptr->content->deleted ? 'D' : ' ');
319 else if (!aptr->content->deleted)
324 mutt_format_s(buf, buflen, prec, ENCODING(aptr->content->encoding));
329 const char dispchar[] = { 'I', 'A', 'F', '-' };
332 if (aptr->content->disposition < sizeof(dispchar))
333 ch = dispchar[aptr->content->disposition];
336 mutt_debug(LL_DEBUG1, "ERROR: invalid content-disposition %d\n",
337 aptr->content->disposition);
340 snprintf(buf, buflen, "%c", ch);
345 mutt_format_s(buf, buflen, prec, TYPE(aptr->content));
349 mutt_format_s(buf, buflen, prec, aptr->content->subtype);
350 else if (!aptr->content->subtype)
356 snprintf(fmt, sizeof(fmt), "%%%sd", prec);
357 snprintf(buf, buflen, fmt, aptr->num + 1);
362 optional = aptr->content->attach_qualifies;
365 snprintf(fmt, sizeof(fmt), "%%%sc", prec);
366 mutt_format_s(buf, buflen, fmt, "Q");
372 if (flags & MUTT_FORMAT_STAT_FILE)
375 stat(aptr->content->filename, &st);
379 l = aptr->content->length;
384 mutt_str_pretty_size(tmp, sizeof(tmp), l);
385 mutt_format_s(buf, buflen, prec, tmp);
394 snprintf(buf, buflen, "%c", aptr->content->tagged ? '*' : ' ');
395 else if (!aptr->content->tagged)
400 mutt_format_s_tree(buf, buflen, prec, NONULL(aptr->tree));
401 else if (!aptr->tree)
406 snprintf(buf, buflen, "%c", aptr->content->unlink ? '-' : ' ');
407 else if (!aptr->content->unlink)
412 optional = ((aptr->content->attach_count + aptr->content->attach_qualifies) != 0);
415 snprintf(fmt, sizeof(fmt), "%%%sd", prec);
416 snprintf(buf, buflen, fmt, aptr->content->attach_count + aptr->content->attach_qualifies);
424 mutt_expando_format(buf, buflen, col, cols, if_str, attach_format_str, data,
425 MUTT_FORMAT_NO_FLAGS);
426 else if (flags & MUTT_FORMAT_OPTIONAL)
427 mutt_expando_format(buf, buflen, col, cols, else_str, attach_format_str,
428 data, MUTT_FORMAT_NO_FLAGS);
433 * attach_make_entry - Format a menu item for the attachment list - Implements Menu::menu_make_entry()
435 static void attach_make_entry(char *buf, size_t buflen, struct Menu *menu, int line)
437 struct AttachCtx *actx = menu->data;
439 mutt_expando_format(buf, buflen, 0, menu->indexwin->cols, NONULL(C_AttachFormat),
440 attach_format_str, (unsigned long) (actx->idx[actx->v2r[line]]),
441 MUTT_FORMAT_ARROWCURSOR);
445 * attach_tag - Tag an attachment - Implements Menu::menu_tag()
447 int attach_tag(struct Menu *menu, int sel, int act)
449 struct AttachCtx *actx = menu->data;
450 struct Body *cur = actx->idx[actx->v2r[sel]]->content;
451 bool ot = cur->tagged;
453 cur->tagged = ((act >= 0) ? act : !cur->tagged);
454 return cur->tagged - ot;
458 * prepend_savedir - Add #C_AttachSaveDir to the beginning of a path
459 * @param buf Buffer for the result
461 static void prepend_savedir(struct Buffer *buf)
463 if (!buf || !buf->data || (buf->data[0] == '/'))
466 struct Buffer *tmp = mutt_buffer_pool_get();
469 mutt_buffer_addstr(tmp, C_AttachSaveDir);
470 if (tmp->dptr[-1] != '/')
471 mutt_buffer_addch(tmp, '/');
474 mutt_buffer_addstr(tmp, "./");
476 mutt_buffer_addstr(tmp, mutt_b2s(buf));
477 mutt_buffer_strcpy(buf, mutt_b2s(tmp));
478 mutt_buffer_pool_release(&tmp);
482 * has_a_message - Determine if the Body has a message (to save)
483 * @param[in] body Body of the message
484 * @retval true if suitable for saving
486 static bool has_a_message(struct Body *body)
488 return (body->email && (body->encoding != ENC_BASE64) &&
489 (body->encoding != ENC_QUOTED_PRINTABLE) &&
490 mutt_is_message_type(body->type, body->subtype));
494 * query_save_attachment - Ask the user if we should save the attachment
495 * @param[in] fp File handle to the attachment (OPTIONAL)
496 * @param[in] body Attachment
498 * @param[out] directory Where the attachment was saved
502 static int query_save_attachment(FILE *fp, struct Body *body, struct Email *e, char **directory)
505 enum SaveAttach opt = MUTT_SAVE_NO_FLAGS;
508 struct Buffer *buf = mutt_buffer_pool_get();
509 struct Buffer *tfile = mutt_buffer_pool_get();
513 if (directory && *directory)
515 mutt_buffer_concat_path(buf, *directory, mutt_path_basename(body->filename));
518 mutt_buffer_strcpy(buf, body->filename);
520 else if (has_a_message(body))
522 mutt_default_save(buf->data, buf->dsize, body->email);
523 mutt_buffer_fix_dptr(buf);
526 prepend_savedir(buf);
528 prompt = _("Save to file: ");
531 if ((mutt_buffer_get_field(prompt, buf, MUTT_FILE | MUTT_CLEAR) != 0) ||
532 mutt_buffer_is_empty(buf))
538 mutt_buffer_expand_path(buf);
540 bool is_message = (fp && has_a_message(body));
546 /* check to make sure that this file is really the one the user wants */
547 rc = mutt_save_confirm(mutt_b2s(buf), &st);
550 prompt = _("Save to file: ");
555 mutt_buffer_strcpy(tfile, mutt_b2s(buf));
559 rc = mutt_check_overwrite(body->filename, mutt_b2s(buf), tfile, &opt, directory);
564 prompt = _("Save to file: ");
569 mutt_message(_("Saving..."));
570 if (mutt_save_attachment(fp, body, mutt_b2s(tfile), opt,
571 (e || !is_message) ? e : body->email) == 0)
573 mutt_message(_("Attachment saved"));
579 prompt = _("Save to file: ");
585 mutt_buffer_pool_release(&buf);
586 mutt_buffer_pool_release(&tfile);
591 * save_without_prompting - Save the attachment, without prompting each time.
592 * @param[in] fp File handle to the attachment (OPTIONAL)
593 * @param[in] body Attachment
598 static int save_without_prompting(FILE *fp, struct Body *body, struct Email *e)
600 enum SaveAttach opt = MUTT_SAVE_NO_FLAGS;
602 struct Buffer *buf = mutt_buffer_pool_get();
603 struct Buffer *tfile = mutt_buffer_pool_get();
607 mutt_buffer_strcpy(buf, body->filename);
609 else if (has_a_message(body))
611 mutt_default_save(buf->data, buf->dsize, body->email);
614 prepend_savedir(buf);
615 mutt_buffer_expand_path(buf);
617 bool is_message = (fp && has_a_message(body));
621 mutt_buffer_strcpy(tfile, mutt_b2s(buf));
625 rc = mutt_check_overwrite(body->filename, mutt_b2s(buf), tfile, &opt, NULL);
626 if (rc == -1) // abort or cancel
630 rc = mutt_save_attachment(fp, body, mutt_b2s(tfile), opt,
631 (e || !is_message) ? e : body->email);
634 mutt_buffer_pool_release(&buf);
635 mutt_buffer_pool_release(&tfile);
640 * mutt_save_attachment_list - Save a list of attachments
641 * @param actx Attachment context
642 * @param fp File handle for the attachment (OPTIONAL)
643 * @param tag If true, only save the tagged attachments
644 * @param top First Attachment
646 * @param menu Menu listing attachments
648 void mutt_save_attachment_list(struct AttachCtx *actx, FILE *fp, bool tag,
649 struct Body *top, struct Email *e, struct Menu *menu)
651 char *directory = NULL;
653 int last = menu ? menu->current : -1;
655 int saved_attachments = 0;
657 struct Buffer *buf = mutt_buffer_pool_get();
658 struct Buffer *tfile = mutt_buffer_pool_get();
660 for (int i = 0; !tag || (i < actx->idxlen); i++)
664 fp = actx->idx[i]->fp;
665 top = actx->idx[i]->content;
667 if (!tag || top->tagged)
671 if (mutt_buffer_is_empty(buf))
673 enum SaveAttach opt = MUTT_SAVE_NO_FLAGS;
675 mutt_buffer_strcpy(buf, mutt_path_basename(NONULL(top->filename)));
676 prepend_savedir(buf);
678 if ((mutt_buffer_get_field(_("Save to file: "), buf, MUTT_FILE | MUTT_CLEAR) != 0) ||
679 mutt_buffer_is_empty(buf))
683 mutt_buffer_expand_path(buf);
684 if (mutt_check_overwrite(top->filename, mutt_b2s(buf), tfile, &opt, NULL))
686 rc = mutt_save_attachment(fp, top, mutt_b2s(tfile), opt, e);
687 if ((rc == 0) && C_AttachSep && (fp_out = fopen(mutt_b2s(tfile), "a")))
689 fprintf(fp_out, "%s", C_AttachSep);
690 mutt_file_fclose(&fp_out);
695 rc = mutt_save_attachment(fp, top, mutt_b2s(tfile), MUTT_SAVE_APPEND, e);
696 if ((rc == 0) && C_AttachSep && (fp_out = fopen(mutt_b2s(tfile), "a")))
698 fprintf(fp_out, "%s", C_AttachSep);
699 mutt_file_fclose(&fp_out);
705 if (tag && menu && top->aptr)
707 menu->oldcurrent = menu->current;
708 menu->current = top->aptr->num;
709 menu_check_recenter(menu);
710 menu->redraw |= REDRAW_MOTION;
714 if (C_AttachSaveWithoutPrompting)
716 // Save each file, with no prompting, using the configured 'AttachSaveDir'
717 rc = save_without_prompting(fp, top, e);
723 // Save each file, prompting the user for the location each time.
724 if (query_save_attachment(fp, top, e, &directory) == -1)
737 menu->oldcurrent = menu->current;
738 menu->current = last;
739 menu_check_recenter(menu);
740 menu->redraw |= REDRAW_MOTION;
743 if (!C_AttachSplit && (rc == 0))
744 mutt_message(_("Attachment saved"));
746 if (C_AttachSaveWithoutPrompting && (rc == 0))
748 mutt_message(ngettext("Attachment saved", "%d attachments saved", saved_attachments),
753 mutt_buffer_pool_release(&buf);
754 mutt_buffer_pool_release(&tfile);
758 * query_pipe_attachment - Ask the user if we should pipe the attachment
759 * @param command Command to pipe the attachment to
760 * @param fp File handle to the attachment (OPTIONAL)
761 * @param body Attachment
762 * @param filter Is this command a filter?
764 static void query_pipe_attachment(char *command, FILE *fp, struct Body *body, bool filter)
766 char tfile[PATH_MAX];
770 char warning[PATH_MAX + 256];
771 snprintf(warning, sizeof(warning),
772 _("WARNING! You are about to overwrite %s, continue?"), body->filename);
773 if (mutt_yesorno(warning, MUTT_NO) != MUTT_YES)
775 mutt_window_clearline(MuttMessageWindow, 0);
778 mutt_mktemp(tfile, sizeof(tfile));
783 if (mutt_pipe_attachment(fp, body, command, tfile))
787 mutt_file_unlink(body->filename);
788 mutt_file_rename(tfile, body->filename);
789 mutt_update_encoding(body);
790 mutt_message(_("Attachment filtered"));
795 if (filter && tfile[0])
796 mutt_file_unlink(tfile);
801 * pipe_attachment - Pipe the attachment to a command
802 * @param fp File handle to the attachment (OPTIONAL)
803 * @param b Attachment
804 * @param state File state for decoding the attachment
806 static void pipe_attachment(FILE *fp, struct Body *b, struct State *state)
808 if (!state || !state->fp_out)
814 mutt_decode_attachment(b, state);
816 state_puts(C_AttachSep, state);
820 FILE *fp_in = fopen(b->filename, "r");
823 mutt_perror("fopen");
826 mutt_file_copy_stream(fp_in, state->fp_out);
827 mutt_file_fclose(&fp_in);
829 state_puts(C_AttachSep, state);
834 * pipe_attachment_list - Pipe a list of attachments to a command
835 * @param command Command to pipe the attachment to
836 * @param actx Attachment context
837 * @param fp File handle to the attachment (OPTIONAL)
838 * @param tag If true, only save the tagged attachments
839 * @param top First Attachment
840 * @param filter Is this command a filter?
841 * @param state File state for decoding the attachments
843 static void pipe_attachment_list(char *command, struct AttachCtx *actx, FILE *fp, bool tag,
844 struct Body *top, bool filter, struct State *state)
846 for (int i = 0; !tag || (i < actx->idxlen); i++)
850 fp = actx->idx[i]->fp;
851 top = actx->idx[i]->content;
853 if (!tag || top->tagged)
855 if (!filter && !C_AttachSplit)
856 pipe_attachment(fp, top, state);
858 query_pipe_attachment(command, fp, top, filter);
866 * mutt_pipe_attachment_list - Pipe a list of attachments to a command
867 * @param actx Attachment context
868 * @param fp File handle to the attachment (OPTIONAL)
869 * @param tag If true, only save the tagged attachments
870 * @param top First Attachment
871 * @param filter Is this command a filter?
873 void mutt_pipe_attachment_list(struct AttachCtx *actx, FILE *fp, bool tag,
874 struct Body *top, bool filter)
876 struct State state = { 0 };
880 filter = false; /* sanity check: we can't filter in the recv case yet */
883 /* perform charset conversion on text attachments when piping */
884 state.flags = MUTT_CHARCONV;
886 if ((mutt_get_field((filter ? _("Filter through: ") : _("Pipe to: ")), buf,
887 sizeof(buf), MUTT_CMD) != 0) ||
893 mutt_expand_path(buf, sizeof(buf));
895 if (!filter && !C_AttachSplit)
898 pid_t pid = mutt_create_filter(buf, &state.fp_out, NULL, NULL);
899 pipe_attachment_list(buf, actx, fp, tag, top, filter, &state);
900 mutt_file_fclose(&state.fp_out);
901 if ((mutt_wait_filter(pid) != 0) || C_WaitKey)
902 mutt_any_key_to_continue(NULL);
905 pipe_attachment_list(buf, actx, fp, tag, top, filter, &state);
909 * can_print - Do we know how to print this attachment type?
910 * @param actx Attachment
911 * @param top Body of email
912 * @param tag Apply to all tagged Attachments
913 * @retval true If (all) the Attachment(s) are printable
915 static bool can_print(struct AttachCtx *actx, struct Body *top, bool tag)
919 for (int i = 0; !tag || (i < actx->idxlen); i++)
922 top = actx->idx[i]->content;
923 snprintf(type, sizeof(type), "%s/%s", TYPE(top), top->subtype);
924 if (!tag || top->tagged)
926 if (!mailcap_lookup(top, type, NULL, MUTT_MC_PRINT))
928 if ((mutt_str_strcasecmp("text/plain", top->subtype) != 0) &&
929 (mutt_str_strcasecmp("application/postscript", top->subtype) != 0))
931 if (!mutt_can_decode(top))
933 /* L10N: s gets replaced by a MIME type, e.g. "text/plain" or
934 application/octet-stream. */
935 mutt_error(_("I don't know how to print %s attachments"), type);
948 * print_attachment_list - Print a list of Attachments
949 * @param actx Attachment context
950 * @param fp File handle to the attachment (OPTIONAL)
951 * @param tag Apply to all tagged Attachments
952 * @param top First Attachment
953 * @param state File state for decoding the attachments
955 static void print_attachment_list(struct AttachCtx *actx, FILE *fp, bool tag,
956 struct Body *top, struct State *state)
960 for (int i = 0; !tag || (i < actx->idxlen); i++)
964 fp = actx->idx[i]->fp;
965 top = actx->idx[i]->content;
967 if (!tag || top->tagged)
969 snprintf(type, sizeof(type), "%s/%s", TYPE(top), top->subtype);
970 if (!C_AttachSplit && !mailcap_lookup(top, type, NULL, MUTT_MC_PRINT))
972 if ((mutt_str_strcasecmp("text/plain", top->subtype) == 0) ||
973 (mutt_str_strcasecmp("application/postscript", top->subtype) == 0))
975 pipe_attachment(fp, top, state);
977 else if (mutt_can_decode(top))
979 /* decode and print */
982 struct Buffer *newfile = mutt_buffer_pool_get();
984 mutt_buffer_mktemp(newfile);
985 if (mutt_decode_save_attachment(fp, top, mutt_b2s(newfile),
986 MUTT_PRINTING, MUTT_SAVE_NO_FLAGS) == 0)
991 "BUG in print_attachment_list(). Please report this. ");
995 fp_in = fopen(mutt_b2s(newfile), "r");
998 mutt_file_copy_stream(fp_in, state->fp_out);
999 mutt_file_fclose(&fp_in);
1001 state_puts(C_AttachSep, state);
1004 mutt_file_unlink(mutt_b2s(newfile));
1005 mutt_buffer_pool_release(&newfile);
1009 mutt_print_attachment(fp, top);
1017 * mutt_print_attachment_list - Print a list of Attachments
1018 * @param actx Attachment context
1019 * @param fp File handle to the attachment (OPTIONAL)
1020 * @param tag Apply to all tagged Attachments
1021 * @param top First Attachment
1023 void mutt_print_attachment_list(struct AttachCtx *actx, FILE *fp, bool tag, struct Body *top)
1026 struct State state = { 0 };
1027 int tagmsgcount = 0;
1030 for (int i = 0; i < actx->idxlen; i++)
1031 if (actx->idx[i]->content->tagged)
1034 snprintf(prompt, sizeof(prompt),
1035 /* L10N: Although we now the precise number of tagged messages, we
1036 do not show it to the user. So feel free to use a "generic
1037 plural" as plural translation if your language has one. */
1038 tag ? ngettext("Print tagged attachment?", "Print %d tagged attachments?", tagmsgcount) :
1039 _("Print attachment?"),
1041 if (query_quadoption(C_Print, prompt) != MUTT_YES)
1046 if (!can_print(actx, top, tag))
1049 pid_t pid = mutt_create_filter(NONULL(C_PrintCommand), &state.fp_out, NULL, NULL);
1050 print_attachment_list(actx, fp, tag, top, &state);
1051 mutt_file_fclose(&state.fp_out);
1052 if ((mutt_wait_filter(pid) != 0) || C_WaitKey)
1053 mutt_any_key_to_continue(NULL);
1056 print_attachment_list(actx, fp, tag, top, &state);
1060 * recvattach_extract_pgp_keys - Extract PGP keys from attachments
1061 * @param actx Attachment context
1062 * @param menu Menu listing attachments
1064 static void recvattach_extract_pgp_keys(struct AttachCtx *actx, struct Menu *menu)
1066 if (!menu->tagprefix)
1067 crypt_pgp_extract_key_from_attachment(CUR_ATTACH->fp, CUR_ATTACH->content);
1070 for (int i = 0; i < actx->idxlen; i++)
1072 if (actx->idx[i]->content->tagged)
1074 crypt_pgp_extract_key_from_attachment(actx->idx[i]->fp, actx->idx[i]->content);
1081 * recvattach_pgp_check_traditional - Is the Attachment inline PGP?
1082 * @param actx Attachment to check
1083 * @param menu Menu listing Attachments
1084 * @retval 1 If the (tagged) Attachment(s) are inline PGP
1086 * @note If the menu->tagprefix is set, all the tagged attachments will be checked.
1088 static int recvattach_pgp_check_traditional(struct AttachCtx *actx, struct Menu *menu)
1092 if (!menu->tagprefix)
1093 rc = crypt_pgp_check_traditional(CUR_ATTACH->fp, CUR_ATTACH->content, true);
1096 for (int i = 0; i < actx->idxlen; i++)
1097 if (actx->idx[i]->content->tagged)
1098 rc = rc || crypt_pgp_check_traditional(actx->idx[i]->fp, actx->idx[i]->content, true);
1105 * recvattach_edit_content_type - Edit the content type of an attachment
1106 * @param actx Attachment context
1107 * @param menu Menu listing Attachments
1110 static void recvattach_edit_content_type(struct AttachCtx *actx,
1111 struct Menu *menu, struct Email *e)
1113 if (!mutt_edit_content_type(e, CUR_ATTACH->content, CUR_ATTACH->fp))
1116 /* The mutt_update_recvattach_menu() will overwrite any changes
1117 * made to a decrypted CUR_ATTACH->content, so warn the user. */
1118 if (CUR_ATTACH->decrypted)
1121 _("Structural changes to decrypted attachments are not supported"));
1124 /* Editing the content type can rewrite the body structure. */
1125 for (int i = 0; i < actx->idxlen; i++)
1126 actx->idx[i]->content = NULL;
1127 mutt_actx_entries_free(actx);
1128 mutt_update_recvattach_menu(actx, menu, true);
1132 * mutt_attach_display_loop - Event loop for the Attachment menu
1133 * @param menu Menu listing Attachments
1134 * @param op Operation, e.g. OP_VIEW_ATTACH
1136 * @param actx Attachment context
1137 * @param recv true if these are received attachments (rather than in compose)
1138 * @retval num Operation performed
1140 int mutt_attach_display_loop(struct Menu *menu, int op, struct Email *e,
1141 struct AttachCtx *actx, bool recv)
1147 case OP_DISPLAY_HEADERS:
1148 bool_str_toggle(Config, "weed", NULL);
1151 case OP_VIEW_ATTACH:
1152 op = mutt_view_attachment(CUR_ATTACH->fp, CUR_ATTACH->content,
1153 MUTT_VA_REGULAR, e, actx);
1157 case OP_MAIN_NEXT_UNDELETED: /* hack */
1158 if (menu->current < menu->max - 1)
1161 op = OP_VIEW_ATTACH;
1167 case OP_MAIN_PREV_UNDELETED: /* hack */
1168 if (menu->current > 0)
1171 op = OP_VIEW_ATTACH;
1177 /* when we edit the content-type, we should redisplay the attachment
1179 mutt_edit_content_type(e, CUR_ATTACH->content, CUR_ATTACH->fp);
1181 recvattach_edit_content_type(actx, menu, e);
1183 mutt_edit_content_type(e, CUR_ATTACH->content, CUR_ATTACH->fp);
1185 menu->redraw |= REDRAW_INDEX;
1186 op = OP_VIEW_ATTACH;
1188 /* functions which are passed through from the pager */
1189 case OP_CHECK_TRADITIONAL:
1190 if (!(WithCrypto & APPLICATION_PGP) || (e && e->security & PGP_TRADITIONAL_CHECKED))
1196 case OP_ATTACH_COLLAPSE:
1203 } while (op != OP_NULL);
1209 * mutt_generate_recvattach_list - Create a list of attachments
1210 * @param actx Attachment context
1212 * @param parts Body of email
1213 * @param fp File to read from
1214 * @param parent_type Type, e.g. #TYPE_MULTIPART
1215 * @param level Attachment depth
1216 * @param decrypted True if attachment has been decrypted
1218 void mutt_generate_recvattach_list(struct AttachCtx *actx, struct Email *e,
1219 struct Body *parts, FILE *fp,
1220 int parent_type, int level, bool decrypted)
1222 struct Body *m = NULL;
1223 struct Body *new_body = NULL;
1224 FILE *fp_new = NULL;
1226 int need_secured, secured;
1228 for (m = parts; m; m = m->next)
1233 if (((WithCrypto & APPLICATION_SMIME) != 0) && (type = mutt_is_application_smime(m)))
1237 if (type & SEC_ENCRYPT)
1239 if (!crypt_valid_passphrase(APPLICATION_SMIME))
1240 goto decrypt_failed;
1243 crypt_smime_getkeys(e->env);
1246 secured = !crypt_smime_decrypt_mime(fp, &fp_new, m, &new_body);
1247 /* If the decrypt/verify-opaque doesn't generate mime output, an empty
1248 * text/plain type will still be returned by mutt_read_mime_header().
1249 * We can't distinguish an actual part from a failure, so only use a
1250 * text/plain that results from a single top-level part. */
1251 if (secured && (new_body->type == TYPE_TEXT) &&
1252 (mutt_str_strcasecmp("plain", new_body->subtype) == 0) &&
1253 ((parts != m) || m->next))
1255 mutt_body_free(&new_body);
1256 mutt_file_fclose(&fp_new);
1257 goto decrypt_failed;
1260 if (secured && (type & SEC_ENCRYPT))
1261 e->security |= SMIME_ENCRYPT;
1264 if (((WithCrypto & APPLICATION_PGP) != 0) &&
1265 (mutt_is_multipart_encrypted(m) || mutt_is_malformed_multipart_pgp_encrypted(m)))
1269 if (!crypt_valid_passphrase(APPLICATION_PGP))
1270 goto decrypt_failed;
1272 secured = !crypt_pgp_decrypt_mime(fp, &fp_new, m, &new_body);
1275 e->security |= PGP_ENCRYPT;
1278 if (need_secured && secured)
1280 mutt_actx_add_fp(actx, fp_new);
1281 mutt_actx_add_body(actx, new_body);
1282 mutt_generate_recvattach_list(actx, e, new_body, fp_new, parent_type, level, 1);
1287 /* Fall through and show the original parts if decryption fails */
1288 if (need_secured && !secured)
1289 mutt_error(_("Can't decrypt encrypted message"));
1291 /* Strip out the top level multipart */
1292 if ((m->type == TYPE_MULTIPART) && m->parts && !need_secured &&
1293 ((parent_type == -1) && mutt_str_strcasecmp("alternative", m->subtype)))
1295 mutt_generate_recvattach_list(actx, e, m->parts, fp, m->type, level, decrypted);
1299 struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
1300 mutt_actx_add_attach(actx, ap);
1305 ap->parent_type = parent_type;
1307 ap->decrypted = decrypted;
1309 if (m->type == TYPE_MULTIPART)
1310 mutt_generate_recvattach_list(actx, e, m->parts, fp, m->type, level + 1, decrypted);
1311 else if (mutt_is_message_type(m->type, m->subtype))
1313 mutt_generate_recvattach_list(actx, m->email, m->parts, fp, m->type,
1314 level + 1, decrypted);
1315 e->security |= m->email->security;
1322 * mutt_attach_init - Create a new Attachment context
1323 * @param actx Attachment context
1325 void mutt_attach_init(struct AttachCtx *actx)
1327 /* Collapse the attachments if '$digest_collapse' is set AND if...
1328 * the outer container is of type 'multipart/digest' */
1329 bool digest = (mutt_str_strcasecmp(actx->email->content->subtype, "digest") == 0);
1331 for (int i = 0; i < actx->idxlen; i++)
1333 actx->idx[i]->content->tagged = false;
1335 /* OR an inner container is of type 'multipart/digest' */
1336 actx->idx[i]->content->collapsed =
1337 (C_DigestCollapse &&
1339 ((actx->idx[i]->content->type == TYPE_MULTIPART) &&
1340 (mutt_str_strcasecmp(actx->idx[i]->content->subtype, "digest") == 0))));
1345 * mutt_update_recvattach_menu - Update the Attachment Menu
1346 * @param actx Attachment context
1347 * @param menu Menu listing Attachments
1348 * @param init If true, create a new Attachments context
1350 static void mutt_update_recvattach_menu(struct AttachCtx *actx, struct Menu *menu, bool init)
1354 mutt_generate_recvattach_list(actx, actx->email, actx->email->content,
1355 actx->fp_root, -1, 0, 0);
1356 mutt_attach_init(actx);
1360 mutt_update_tree(actx);
1362 menu->max = actx->vcount;
1364 if (menu->current >= menu->max)
1365 menu->current = menu->max - 1;
1366 menu_check_recenter(menu);
1367 menu->redraw |= REDRAW_INDEX;
1371 * attach_collapse - Close the tree of the current attachment
1372 * @param actx Attachment context
1373 * @param menu Menu listing Attachments
1375 static void attach_collapse(struct AttachCtx *actx, struct Menu *menu)
1377 int rindex, curlevel;
1379 CUR_ATTACH->content->collapsed = !CUR_ATTACH->content->collapsed;
1380 /* When expanding, expand all the children too */
1381 if (CUR_ATTACH->content->collapsed)
1384 curlevel = CUR_ATTACH->level;
1385 rindex = actx->v2r[menu->current] + 1;
1387 while ((rindex < actx->idxlen) && (actx->idx[rindex]->level > curlevel))
1389 if (C_DigestCollapse && (actx->idx[rindex]->content->type == TYPE_MULTIPART) &&
1390 !mutt_str_strcasecmp(actx->idx[rindex]->content->subtype, "digest"))
1392 actx->idx[rindex]->content->collapsed = true;
1396 actx->idx[rindex]->content->collapsed = false;
1403 * mutt_view_attachments - Show the attachments in a Menu
1406 void mutt_view_attachments(struct Email *e)
1411 struct Mailbox *m = Context ? Context->mailbox : NULL;
1413 /* make sure we have parsed this message */
1414 mutt_parse_mime_message(m, e);
1416 mutt_message_hook(m, e, MUTT_MESSAGE_HOOK);
1418 struct Message *msg = mx_msg_open(m, e->msgno);
1422 struct Menu *menu = mutt_menu_new(MENU_ATTACH);
1423 menu->title = _("Attachments");
1424 menu->menu_make_entry = attach_make_entry;
1425 menu->menu_tag = attach_tag;
1426 menu->help = mutt_compile_help(helpstr, sizeof(helpstr), MENU_ATTACH, AttachHelp);
1427 mutt_menu_push_current(menu);
1429 struct AttachCtx *actx = mutt_actx_new();
1431 actx->fp_root = msg->fp;
1432 mutt_update_recvattach_menu(actx, menu, true);
1437 op = mutt_menu_loop(menu);
1442 case OP_ATTACH_VIEW_MAILCAP:
1443 mutt_view_attachment(CUR_ATTACH->fp, CUR_ATTACH->content, MUTT_VA_MAILCAP, e, actx);
1444 menu->redraw = REDRAW_FULL;
1447 case OP_ATTACH_VIEW_TEXT:
1448 mutt_view_attachment(CUR_ATTACH->fp, CUR_ATTACH->content, MUTT_VA_AS_TEXT, e, actx);
1449 menu->redraw = REDRAW_FULL;
1452 case OP_DISPLAY_HEADERS:
1453 case OP_VIEW_ATTACH:
1454 op = mutt_attach_display_loop(menu, op, e, actx, true);
1455 menu->redraw = REDRAW_FULL;
1458 case OP_ATTACH_COLLAPSE:
1459 if (!CUR_ATTACH->content->parts)
1461 mutt_error(_("There are no subparts to show"));
1464 attach_collapse(actx, menu);
1465 mutt_update_recvattach_menu(actx, menu, false);
1468 case OP_FORGET_PASSPHRASE:
1469 crypt_forget_passphrase();
1472 case OP_EXTRACT_KEYS:
1473 if (WithCrypto & APPLICATION_PGP)
1475 recvattach_extract_pgp_keys(actx, menu);
1476 menu->redraw = REDRAW_FULL;
1480 case OP_CHECK_TRADITIONAL:
1481 if (((WithCrypto & APPLICATION_PGP) != 0) &&
1482 recvattach_pgp_check_traditional(actx, menu))
1484 e->security = crypt_query(NULL);
1485 menu->redraw = REDRAW_FULL;
1490 mutt_print_attachment_list(actx, CUR_ATTACH->fp, menu->tagprefix,
1491 CUR_ATTACH->content);
1495 mutt_pipe_attachment_list(actx, CUR_ATTACH->fp, menu->tagprefix,
1496 CUR_ATTACH->content, false);
1500 mutt_save_attachment_list(actx, CUR_ATTACH->fp, menu->tagprefix,
1501 CUR_ATTACH->content, e, menu);
1503 if (!menu->tagprefix && C_Resolve && (menu->current < menu->max - 1))
1506 menu->redraw = REDRAW_MOTION_RESYNC | REDRAW_FULL;
1513 if (m->magic == MUTT_POP)
1516 mutt_error(_("Can't delete attachment from POP server"));
1522 if (m->magic == MUTT_NNTP)
1525 mutt_error(_("Can't delete attachment from news server"));
1530 if ((WithCrypto != 0) && (e->security & SEC_ENCRYPT))
1532 mutt_message(_("Deletion of attachments from encrypted messages is "
1536 if ((WithCrypto != 0) && (e->security & (SEC_SIGN | SEC_PARTSIGN)))
1538 mutt_message(_("Deletion of attachments from signed messages may "
1539 "invalidate the signature"));
1541 if (!menu->tagprefix)
1543 if (CUR_ATTACH->parent_type == TYPE_MULTIPART)
1545 CUR_ATTACH->content->deleted = true;
1546 if (C_Resolve && (menu->current < menu->max - 1))
1549 menu->redraw = REDRAW_MOTION_RESYNC;
1552 menu->redraw = REDRAW_CURRENT;
1557 _("Only deletion of multipart attachments is supported"));
1562 for (int i = 0; i < menu->max; i++)
1564 if (actx->idx[i]->content->tagged)
1566 if (actx->idx[i]->parent_type == TYPE_MULTIPART)
1568 actx->idx[i]->content->deleted = true;
1569 menu->redraw = REDRAW_INDEX;
1574 _("Only deletion of multipart attachments is supported"));
1583 if (!menu->tagprefix)
1585 CUR_ATTACH->content->deleted = false;
1586 if (C_Resolve && (menu->current < menu->max - 1))
1589 menu->redraw = REDRAW_MOTION_RESYNC;
1592 menu->redraw = REDRAW_CURRENT;
1596 for (int i = 0; i < menu->max; i++)
1598 if (actx->idx[i]->content->tagged)
1600 actx->idx[i]->content->deleted = false;
1601 menu->redraw = REDRAW_INDEX;
1609 mutt_attach_resend(CUR_ATTACH->fp, actx,
1610 menu->tagprefix ? NULL : CUR_ATTACH->content);
1611 menu->redraw = REDRAW_FULL;
1614 case OP_BOUNCE_MESSAGE:
1616 mutt_attach_bounce(m, CUR_ATTACH->fp, actx,
1617 menu->tagprefix ? NULL : CUR_ATTACH->content);
1618 menu->redraw = REDRAW_FULL;
1621 case OP_FORWARD_MESSAGE:
1623 mutt_attach_forward(CUR_ATTACH->fp, e, actx,
1624 menu->tagprefix ? NULL : CUR_ATTACH->content, SEND_NO_FLAGS);
1625 menu->redraw = REDRAW_FULL;
1629 case OP_FORWARD_TO_GROUP:
1631 mutt_attach_forward(CUR_ATTACH->fp, e, actx,
1632 menu->tagprefix ? NULL : CUR_ATTACH->content, SEND_NEWS);
1633 menu->redraw = REDRAW_FULL;
1639 if (!CUR_ATTACH->content->email->env->followup_to ||
1640 (mutt_str_strcasecmp(CUR_ATTACH->content->email->env->followup_to, "poster") != 0) ||
1641 (query_quadoption(C_FollowupToPoster,
1642 _("Reply by mail as poster prefers?")) != MUTT_YES))
1644 mutt_attach_reply(CUR_ATTACH->fp, e, actx,
1645 menu->tagprefix ? NULL : CUR_ATTACH->content,
1646 SEND_NEWS | SEND_REPLY);
1647 menu->redraw = REDRAW_FULL;
1653 case OP_GROUP_REPLY:
1654 case OP_GROUP_CHAT_REPLY:
1659 SendFlags flags = SEND_REPLY;
1660 if (op == OP_GROUP_REPLY)
1661 flags |= SEND_GROUP_REPLY;
1662 else if (op == OP_GROUP_CHAT_REPLY)
1663 flags |= SEND_GROUP_CHAT_REPLY;
1664 else if (op == OP_LIST_REPLY)
1665 flags |= SEND_LIST_REPLY;
1667 mutt_attach_reply(CUR_ATTACH->fp, e, actx,
1668 menu->tagprefix ? NULL : CUR_ATTACH->content, flags);
1669 menu->redraw = REDRAW_FULL;
1673 case OP_COMPOSE_TO_SENDER:
1675 mutt_attach_mail_sender(CUR_ATTACH->fp, e, actx,
1676 menu->tagprefix ? NULL : CUR_ATTACH->content);
1677 menu->redraw = REDRAW_FULL;
1681 recvattach_edit_content_type(actx, menu, e);
1682 menu->redraw |= REDRAW_INDEX;
1686 mx_msg_close(m, &msg);
1688 e->attach_del = false;
1689 for (int i = 0; i < actx->idxlen; i++)
1691 if (actx->idx[i]->content && actx->idx[i]->content->deleted)
1693 e->attach_del = true;
1700 mutt_actx_free(&actx);
1702 mutt_menu_pop_current(menu);
1703 mutt_menu_free(&menu);