mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2000,2002,2007,2010 Michael R. Elkins <me@mutt.org>
3 * Copyright (C) 1999-2006 Thomas Roessler <roessler@does-not-exist.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#if HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "mutt.h"
25#include "mutt_curses.h"
26#include "mutt_menu.h"
27#include "rfc1524.h"
28#include "mime.h"
29#include "mailbox.h"
30#include "attach.h"
31#include "mapping.h"
32#include "mx.h"
33#include "mutt_crypt.h"
34
35#include <ctype.h>
36#include <stdlib.h>
37#include <unistd.h>
38#include <sys/wait.h>
39#include <sys/stat.h>
40#include <string.h>
41#include <errno.h>
42
43static const char *Mailbox_is_read_only = N_("Mailbox is read-only.");
44
45#define CHECK_READONLY if (Context->readonly) \
46{\
47 mutt_flushinp (); \
48 mutt_error _(Mailbox_is_read_only); \
49 break; \
50}
51
52static const struct mapping_t AttachHelp[] = {
53 { N_("Exit"), OP_EXIT },
54 { N_("Save"), OP_SAVE },
55 { N_("Pipe"), OP_PIPE },
56 { N_("Print"), OP_PRINT },
57 { N_("Help"), OP_HELP },
58 { NULL, 0 }
59};
60
61void mutt_update_tree (ATTACHPTR **idx, short idxlen)
62{
63 char buf[STRING];
64 char *s;
65 int x;
66
67 for (x = 0; x < idxlen; x++)
68 {
69 idx[x]->num = x;
70 if (2 * (idx[x]->level + 2) < sizeof (buf))
71 {
72 if (idx[x]->level)
73 {
74 s = buf + 2 * (idx[x]->level - 1);
75 *s++ = (idx[x]->content->next) ? MUTT_TREE_LTEE : MUTT_TREE_LLCORNER;
76 *s++ = MUTT_TREE_HLINE;
77 *s++ = MUTT_TREE_RARROW;
78 }
79 else
80 s = buf;
81 *s = 0;
82 }
83
84 if (idx[x]->tree)
85 {
86 if (mutt_strcmp (idx[x]->tree, buf) != 0)
87 mutt_str_replace (&idx[x]->tree, buf);
88 }
89 else
90 idx[x]->tree = safe_strdup (buf);
91
92 if (2 * (idx[x]->level + 2) < sizeof (buf) && idx[x]->level)
93 {
94 s = buf + 2 * (idx[x]->level - 1);
95 *s++ = (idx[x]->content->next) ? '\005' : '\006';
96 *s++ = '\006';
97 }
98 }
99}
100
101ATTACHPTR **mutt_gen_attach_list (BODY *m,
102 int parent_type,
103 ATTACHPTR **idx,
104 short *idxlen,
105 short *idxmax,
106 int level,
107 int compose)
108{
109 ATTACHPTR *new;
110 int i;
111
112 for (; m; m = m->next)
113 {
114 if (*idxlen == *idxmax)
115 {
116 safe_realloc (&idx, sizeof (ATTACHPTR *) * ((*idxmax) += 5));
117 for (i = *idxlen; i < *idxmax; i++)
118 idx[i] = NULL;
119 }
120
121 if (m->type == TYPEMULTIPART && m->parts
122 && (compose || (parent_type == -1 && ascii_strcasecmp ("alternative", m->subtype)))
123 && (!(WithCrypto & APPLICATION_PGP) || !mutt_is_multipart_encrypted(m))
124 )
125 {
126 idx = mutt_gen_attach_list (m->parts, m->type, idx, idxlen, idxmax, level, compose);
127 }
128 else
129 {
130 if (!idx[*idxlen])
131 idx[*idxlen] = (ATTACHPTR *) safe_calloc (1, sizeof (ATTACHPTR));
132
133 new = idx[(*idxlen)++];
134 new->content = m;
135 m->aptr = new;
136 new->parent_type = parent_type;
137 new->level = level;
138
139 /* We don't support multipart messages in the compose menu yet */
140 if (!compose && !m->collapsed &&
141 ((m->type == TYPEMULTIPART
142 && (!(WithCrypto & APPLICATION_PGP)
143 || !mutt_is_multipart_encrypted (m))
144 )
145 || mutt_is_message_type(m->type, m->subtype)))
146 {
147 idx = mutt_gen_attach_list (m->parts, m->type, idx, idxlen, idxmax, level + 1, compose);
148 }
149 }
150 }
151
152 if (level == 0)
153 mutt_update_tree (idx, *idxlen);
154
155 return (idx);
156}
157
158/* %c = character set: convert?
159 * %C = character set
160 * %D = deleted flag
161 * %d = description
162 * %e = MIME content-transfer-encoding
163 * %F = filename for content-disposition header
164 * %f = filename
165 * %I = content-disposition, either I (inline) or A (attachment)
166 * %t = tagged flag
167 * %T = tree chars
168 * %m = major MIME type
169 * %M = MIME subtype
170 * %n = attachment number
171 * %s = size
172 * %u = unlink
173 */
174const char *mutt_attach_fmt (char *dest,
175 size_t destlen,
176 size_t col,
177 int cols,
178 char op,
179 const char *src,
180 const char *prefix,
181 const char *ifstring,
182 const char *elsestring,
183 unsigned long data,
184 format_flag flags)
185{
186 char fmt[16];
187 char tmp[SHORT_STRING];
188 char charset[SHORT_STRING];
189 ATTACHPTR *aptr = (ATTACHPTR *) data;
190 int optional = (flags & MUTT_FORMAT_OPTIONAL);
191 size_t l;
192
193 switch (op)
194 {
195 case 'C':
196 if (!optional)
197 {
198 if (mutt_is_text_part (aptr->content) &&
199 mutt_get_body_charset (charset, sizeof (charset), aptr->content))
200 mutt_format_s (dest, destlen, prefix, charset);
201 else
202 mutt_format_s (dest, destlen, prefix, "");
203 }
204 else if (!mutt_is_text_part (aptr->content) ||
205 !mutt_get_body_charset (charset, sizeof (charset), aptr->content))
206 optional = 0;
207 break;
208 case 'c':
209 /* XXX */
210 if (!optional)
211 {
212 snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
213 snprintf (dest, destlen, fmt, aptr->content->type != TYPETEXT ||
214 aptr->content->noconv ? 'n' : 'c');
215 }
216 else if (aptr->content->type != TYPETEXT || aptr->content->noconv)
217 optional = 0;
218 break;
219 case 'd':
220 if(!optional)
221 {
222 if (aptr->content->description)
223 {
224 mutt_format_s (dest, destlen, prefix, aptr->content->description);
225 break;
226 }
227 if (mutt_is_message_type(aptr->content->type, aptr->content->subtype) &&
228 MsgFmt && aptr->content->hdr)
229 {
230 char s[SHORT_STRING];
231 _mutt_make_string (s, sizeof (s), MsgFmt, NULL, aptr->content->hdr,
232 MUTT_FORMAT_FORCESUBJ | MUTT_FORMAT_MAKEPRINT | MUTT_FORMAT_ARROWCURSOR);
233 if (*s)
234 {
235 mutt_format_s (dest, destlen, prefix, s);
236 break;
237 }
238 }
239 if (!aptr->content->d_filename && !aptr->content->filename)
240 {
241 mutt_format_s (dest, destlen, prefix, "<no description>");
242 break;
243 }
244 }
245 else if(aptr->content->description ||
246 (mutt_is_message_type (aptr->content->type, aptr->content->subtype)
247 && MsgFmt && aptr->content->hdr))
248 break;
249 /* FALLS THROUGH TO 'F' */
250 case 'F':
251 if (!optional)
252 {
253 if (aptr->content->d_filename)
254 {
255 mutt_format_s (dest, destlen, prefix, aptr->content->d_filename);
256 break;
257 }
258 }
259 else if (!aptr->content->d_filename && !aptr->content->filename)
260 {
261 optional = 0;
262 break;
263 }
264 /* FALLS THROUGH TO 'f' */
265 case 'f':
266 if(!optional)
267 {
268 if (aptr->content->filename && *aptr->content->filename == '/')
269 {
270 char path[_POSIX_PATH_MAX];
271
272 strfcpy (path, aptr->content->filename, sizeof (path));
273 mutt_pretty_mailbox (path, sizeof (path));
274 mutt_format_s (dest, destlen, prefix, path);
275 }
276 else
277 mutt_format_s (dest, destlen, prefix, NONULL (aptr->content->filename));
278 }
279 else if(!aptr->content->filename)
280 optional = 0;
281 break;
282 case 'D':
283 if(!optional)
284 snprintf (dest, destlen, "%c", aptr->content->deleted ? 'D' : ' ');
285 else if(!aptr->content->deleted)
286 optional = 0;
287 break;
288 case 'e':
289 if(!optional)
290 mutt_format_s (dest, destlen, prefix,
291 ENCODING (aptr->content->encoding));
292 break;
293 case 'I':
294 if (!optional)
295 {
296 const char dispchar[] = { 'I', 'A', 'F', '-' };
297 char ch;
298
299 if (aptr->content->disposition < sizeof(dispchar))
300 ch = dispchar[aptr->content->disposition];
301 else
302 {
303 dprint(1, (debugfile, "ERROR: invalid content-disposition %d\n", aptr->content->disposition));
304 ch = '!';
305 }
306 snprintf (dest, destlen, "%c", ch);
307 }
308 break;
309 case 'm':
310 if(!optional)
311 mutt_format_s (dest, destlen, prefix, TYPE (aptr->content));
312 break;
313 case 'M':
314 if(!optional)
315 mutt_format_s (dest, destlen, prefix, aptr->content->subtype);
316 else if(!aptr->content->subtype)
317 optional = 0;
318 break;
319 case 'n':
320 if(!optional)
321 {
322 snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
323 snprintf (dest, destlen, fmt, aptr->num + 1);
324 }
325 break;
326 case 'Q':
327 if (optional)
328 optional = aptr->content->attach_qualifies;
329 else {
330 snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
331 mutt_format_s (dest, destlen, fmt, "Q");
332 }
333 break;
334 case 's':
335 if (flags & MUTT_FORMAT_STAT_FILE)
336 {
337 struct stat st;
338 stat (aptr->content->filename, &st);
339 l = st.st_size;
340 }
341 else
342 l = aptr->content->length;
343
344 if(!optional)
345 {
346 mutt_pretty_size (tmp, sizeof(tmp), l);
347 mutt_format_s (dest, destlen, prefix, tmp);
348 }
349 else if (l == 0)
350 optional = 0;
351
352 break;
353 case 't':
354 if(!optional)
355 snprintf (dest, destlen, "%c", aptr->content->tagged ? '*' : ' ');
356 else if(!aptr->content->tagged)
357 optional = 0;
358 break;
359 case 'T':
360 if(!optional)
361 mutt_format_s_tree (dest, destlen, prefix, NONULL (aptr->tree));
362 else if (!aptr->tree)
363 optional = 0;
364 break;
365 case 'u':
366 if(!optional)
367 snprintf (dest, destlen, "%c", aptr->content->unlink ? '-' : ' ');
368 else if (!aptr->content->unlink)
369 optional = 0;
370 break;
371 case 'X':
372 if (optional)
373 optional = (aptr->content->attach_count + aptr->content->attach_qualifies) != 0;
374 else
375 {
376 snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
377 snprintf (dest, destlen, fmt, aptr->content->attach_count + aptr->content->attach_qualifies);
378 }
379 break;
380 default:
381 *dest = 0;
382 }
383
384 if (optional)
385 mutt_FormatString (dest, destlen, col, cols, ifstring, mutt_attach_fmt, data, 0);
386 else if (flags & MUTT_FORMAT_OPTIONAL)
387 mutt_FormatString (dest, destlen, col, cols, elsestring, mutt_attach_fmt, data, 0);
388 return (src);
389}
390
391static void attach_entry (char *b, size_t blen, MUTTMENU *menu, int num)
392{
393 mutt_FormatString (b, blen, 0, MuttIndexWindow->cols, NONULL (AttachFormat), mutt_attach_fmt, (unsigned long) (((ATTACHPTR **)menu->data)[num]), MUTT_FORMAT_ARROWCURSOR);
394}
395
396int mutt_tag_attach (MUTTMENU *menu, int n, int m)
397{
398 BODY *cur = ((ATTACHPTR **) menu->data)[n]->content;
399 int ot = cur->tagged;
400
401 cur->tagged = (m >= 0 ? m : !cur->tagged);
402 return cur->tagged - ot;
403}
404
405int mutt_is_message_type (int type, const char *subtype)
406{
407 if (type != TYPEMESSAGE)
408 return 0;
409
410 subtype = NONULL(subtype);
411 return (ascii_strcasecmp (subtype, "rfc822") == 0 || ascii_strcasecmp (subtype, "news") == 0);
412}
413
414static void prepend_curdir (char *dst, size_t dstlen)
415{
416 size_t l;
417
418 if (!dst || !*dst || *dst == '/' || dstlen < 3 ||
419 /* XXX bad modularization, these are special to mutt_expand_path() */
420 !strchr ("~=+@<>!-^", *dst))
421 return;
422
423 dstlen -= 3;
424 l = strlen (dst) + 2;
425 l = (l > dstlen ? dstlen : l);
426 memmove (dst + 2, dst, l);
427 dst[0] = '.';
428 dst[1] = '/';
429 dst[l + 2] = 0;
430}
431
432static int mutt_query_save_attachment (FILE *fp, BODY *body, HEADER *hdr, char **directory)
433{
434 char *prompt;
435 char buf[_POSIX_PATH_MAX], tfile[_POSIX_PATH_MAX];
436 int is_message;
437 int append = 0;
438 int rc;
439
440 if (body->filename)
441 {
442 if (directory && *directory)
443 mutt_concat_path (buf, *directory, mutt_basename (body->filename), sizeof (buf));
444 else
445 strfcpy (buf, body->filename, sizeof (buf));
446 }
447 else if(body->hdr &&
448 body->encoding != ENCBASE64 &&
449 body->encoding != ENCQUOTEDPRINTABLE &&
450 mutt_is_message_type(body->type, body->subtype))
451 mutt_default_save(buf, sizeof(buf), body->hdr);
452 else
453 buf[0] = 0;
454
455 prepend_curdir (buf, sizeof (buf));
456
457 prompt = _("Save to file: ");
458 while (prompt)
459 {
460 if (mutt_get_field (prompt, buf, sizeof (buf), MUTT_FILE | MUTT_CLEAR) != 0
461 || !buf[0])
462 {
463 mutt_clear_error ();
464 return -1;
465 }
466
467 prompt = NULL;
468 mutt_expand_path (buf, sizeof (buf));
469
470 is_message = (fp &&
471 body->hdr &&
472 body->encoding != ENCBASE64 &&
473 body->encoding != ENCQUOTEDPRINTABLE &&
474 mutt_is_message_type (body->type, body->subtype));
475
476 if (is_message)
477 {
478 struct stat st;
479
480 /* check to make sure that this file is really the one the user wants */
481 if ((rc = mutt_save_confirm (buf, &st)) == 1)
482 {
483 prompt = _("Save to file: ");
484 continue;
485 }
486 else if (rc == -1)
487 return -1;
488 strfcpy(tfile, buf, sizeof(tfile));
489 }
490 else
491 {
492 if ((rc = mutt_check_overwrite (body->filename, buf, tfile, sizeof (tfile), &append, directory)) == -1)
493 return -1;
494 else if (rc == 1)
495 {
496 prompt = _("Save to file: ");
497 continue;
498 }
499 }
500
501 mutt_message _("Saving...");
502 if (mutt_save_attachment (fp, body, tfile, append, (hdr || !is_message) ? hdr : body->hdr) == 0)
503 {
504 mutt_message _("Attachment saved.");
505 return 0;
506 }
507 else
508 {
509 prompt = _("Save to file: ");
510 continue;
511 }
512 }
513 return 0;
514}
515
516void mutt_save_attachment_list (FILE *fp, int tag, BODY *top, HEADER *hdr, MUTTMENU *menu)
517{
518 char buf[_POSIX_PATH_MAX], tfile[_POSIX_PATH_MAX];
519 char *directory = NULL;
520 int rc = 1;
521 int last = menu ? menu->current : -1;
522 FILE *fpout;
523
524 buf[0] = 0;
525
526 for (; top; top = top->next)
527 {
528 if (!tag || top->tagged)
529 {
530 if (!option (OPTATTACHSPLIT))
531 {
532 if (!buf[0])
533 {
534 int append = 0;
535
536 strfcpy (buf, mutt_basename (NONULL (top->filename)), sizeof (buf));
537 prepend_curdir (buf, sizeof (buf));
538
539 if (mutt_get_field (_("Save to file: "), buf, sizeof (buf),
540 MUTT_FILE | MUTT_CLEAR) != 0 || !buf[0])
541 return;
542 mutt_expand_path (buf, sizeof (buf));
543 if (mutt_check_overwrite (top->filename, buf, tfile,
544 sizeof (tfile), &append, NULL))
545 return;
546 rc = mutt_save_attachment (fp, top, tfile, append, hdr);
547 if (rc == 0 && AttachSep && (fpout = fopen (tfile,"a")) != NULL)
548 {
549 fprintf(fpout, "%s", AttachSep);
550 safe_fclose (&fpout);
551 }
552 }
553 else
554 {
555 rc = mutt_save_attachment (fp, top, tfile, MUTT_SAVE_APPEND, hdr);
556 if (rc == 0 && AttachSep && (fpout = fopen (tfile,"a")) != NULL)
557 {
558 fprintf(fpout, "%s", AttachSep);
559 safe_fclose (&fpout);
560 }
561 }
562 }
563 else
564 {
565 if (tag && menu && top->aptr)
566 {
567 menu->oldcurrent = menu->current;
568 menu->current = top->aptr->num;
569 menu_check_recenter (menu);
570 menu->redraw |= REDRAW_MOTION;
571
572 menu_redraw (menu);
573 }
574 if (mutt_query_save_attachment (fp, top, hdr, &directory) == -1)
575 break;
576 }
577 }
578 else if (top->parts)
579 mutt_save_attachment_list (fp, 1, top->parts, hdr, menu);
580 if (!tag)
581 break;
582 }
583
584 FREE (&directory);
585
586 if (tag && menu)
587 {
588 menu->oldcurrent = menu->current;
589 menu->current = last;
590 menu_check_recenter (menu);
591 menu->redraw |= REDRAW_MOTION;
592 }
593
594 if (!option (OPTATTACHSPLIT) && (rc == 0))
595 mutt_message _("Attachment saved.");
596}
597
598static void
599mutt_query_pipe_attachment (char *command, FILE *fp, BODY *body, int filter)
600{
601 char tfile[_POSIX_PATH_MAX];
602 char warning[STRING+_POSIX_PATH_MAX];
603
604 if (filter)
605 {
606 snprintf (warning, sizeof (warning),
607 _("WARNING! You are about to overwrite %s, continue?"),
608 body->filename);
609 if (mutt_yesorno (warning, MUTT_NO) != MUTT_YES) {
610 mutt_window_clearline (MuttMessageWindow, 0);
611 return;
612 }
613 mutt_mktemp (tfile, sizeof (tfile));
614 }
615 else
616 tfile[0] = 0;
617
618 if (mutt_pipe_attachment (fp, body, command, tfile))
619 {
620 if (filter)
621 {
622 mutt_unlink (body->filename);
623 mutt_rename_file (tfile, body->filename);
624 mutt_update_encoding (body);
625 mutt_message _("Attachment filtered.");
626 }
627 }
628 else
629 {
630 if (filter && tfile[0])
631 mutt_unlink (tfile);
632 }
633}
634
635static void pipe_attachment (FILE *fp, BODY *b, STATE *state)
636{
637 FILE *ifp;
638
639 if (fp)
640 {
641 state->fpin = fp;
642 mutt_decode_attachment (b, state);
643 if (AttachSep)
644 state_puts (AttachSep, state);
645 }
646 else
647 {
648 if ((ifp = fopen (b->filename, "r")) == NULL)
649 {
650 mutt_perror ("fopen");
651 return;
652 }
653 mutt_copy_stream (ifp, state->fpout);
654 safe_fclose (&ifp);
655 if (AttachSep)
656 state_puts (AttachSep, state);
657 }
658}
659
660static void
661pipe_attachment_list (char *command, FILE *fp, int tag, BODY *top, int filter,
662 STATE *state)
663{
664 for (; top; top = top->next)
665 {
666 if (!tag || top->tagged)
667 {
668 if (!filter && !option (OPTATTACHSPLIT))
669 pipe_attachment (fp, top, state);
670 else
671 mutt_query_pipe_attachment (command, fp, top, filter);
672 }
673 else if (top->parts)
674 pipe_attachment_list (command, fp, tag, top->parts, filter, state);
675 if (!tag)
676 break;
677 }
678}
679
680void mutt_pipe_attachment_list (FILE *fp, int tag, BODY *top, int filter)
681{
682 STATE state;
683 char buf[SHORT_STRING];
684 pid_t thepid;
685
686 if (fp)
687 filter = 0; /* sanity check: we can't filter in the recv case yet */
688
689 buf[0] = 0;
690 memset (&state, 0, sizeof (STATE));
691 /* perform charset conversion on text attachments when piping */
692 state.flags = MUTT_CHARCONV;
693
694 if (mutt_get_field ((filter ? _("Filter through: ") : _("Pipe to: ")),
695 buf, sizeof (buf), MUTT_CMD) != 0 || !buf[0])
696 return;
697
698 mutt_expand_path (buf, sizeof (buf));
699
700 if (!filter && !option (OPTATTACHSPLIT))
701 {
702 mutt_endwin (NULL);
703 thepid = mutt_create_filter (buf, &state.fpout, NULL, NULL);
704 pipe_attachment_list (buf, fp, tag, top, filter, &state);
705 safe_fclose (&state.fpout);
706 if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY))
707 mutt_any_key_to_continue (NULL);
708 }
709 else
710 pipe_attachment_list (buf, fp, tag, top, filter, &state);
711}
712
713static int can_print (BODY *top, int tag)
714{
715 char type [STRING];
716
717 for (; top; top = top->next)
718 {
719 snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype);
720 if (!tag || top->tagged)
721 {
722 if (!rfc1524_mailcap_lookup (top, type, NULL, MUTT_PRINT))
723 {
724 if (ascii_strcasecmp ("text/plain", top->subtype) &&
725 ascii_strcasecmp ("application/postscript", top->subtype))
726 {
727 if (!mutt_can_decode (top))
728 {
729 mutt_error (_("I don't know how to print %s attachments!"), type);
730 return (0);
731 }
732 }
733 }
734 }
735 else if (top->parts)
736 return (can_print (top->parts, tag));
737 if (!tag)
738 break;
739 }
740 return (1);
741}
742
743static void print_attachment_list (FILE *fp, int tag, BODY *top, STATE *state)
744{
745 char type [STRING];
746
747
748 for (; top; top = top->next)
749 {
750 if (!tag || top->tagged)
751 {
752 snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype);
753 if (!option (OPTATTACHSPLIT) && !rfc1524_mailcap_lookup (top, type, NULL, MUTT_PRINT))
754 {
755 if (!ascii_strcasecmp ("text/plain", top->subtype) ||
756 !ascii_strcasecmp ("application/postscript", top->subtype))
757 pipe_attachment (fp, top, state);
758 else if (mutt_can_decode (top))
759 {
760 /* decode and print */
761
762 char newfile[_POSIX_PATH_MAX] = "";
763 FILE *ifp;
764
765 mutt_mktemp (newfile, sizeof (newfile));
766 if (mutt_decode_save_attachment (fp, top, newfile, MUTT_PRINTING, 0) == 0)
767 {
768 if ((ifp = fopen (newfile, "r")) != NULL)
769 {
770 mutt_copy_stream (ifp, state->fpout);
771 safe_fclose (&ifp);
772 if (AttachSep)
773 state_puts (AttachSep, state);
774 }
775 }
776 mutt_unlink (newfile);
777 }
778 }
779 else
780 mutt_print_attachment (fp, top);
781 }
782 else if (top->parts)
783 print_attachment_list (fp, tag, top->parts, state);
784 if (!tag)
785 return;
786 }
787}
788
789void mutt_print_attachment_list (FILE *fp, int tag, BODY *top)
790{
791 STATE state;
792
793 pid_t thepid;
794 if (query_quadoption (OPT_PRINT, tag ? _("Print tagged attachment(s)?") : _("Print attachment?")) != MUTT_YES)
795 return;
796
797 if (!option (OPTATTACHSPLIT))
798 {
799 if (!can_print (top, tag))
800 return;
801 mutt_endwin (NULL);
802 memset (&state, 0, sizeof (STATE));
803 thepid = mutt_create_filter (NONULL (PrintCmd), &state.fpout, NULL, NULL);
804 print_attachment_list (fp, tag, top, &state);
805 safe_fclose (&state.fpout);
806 if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY))
807 mutt_any_key_to_continue (NULL);
808 }
809 else
810 print_attachment_list (fp, tag, top, &state);
811}
812
813static void
814mutt_update_attach_index (BODY *cur, ATTACHPTR ***idxp,
815 short *idxlen, short *idxmax,
816 MUTTMENU *menu)
817{
818 ATTACHPTR **idx = *idxp;
819 while (--(*idxlen) >= 0)
820 idx[(*idxlen)]->content = NULL;
821 *idxlen = 0;
822
823 idx = *idxp = mutt_gen_attach_list (cur, -1, idx, idxlen, idxmax, 0, 0);
824
825 menu->max = *idxlen;
826 menu->data = *idxp;
827
828 if (menu->current >= menu->max)
829 menu->current = menu->max - 1;
830 menu_check_recenter (menu);
831 menu->redraw |= REDRAW_INDEX;
832
833}
834
835
836int
837mutt_attach_display_loop (MUTTMENU *menu, int op, FILE *fp, HEADER *hdr,
838 BODY *cur, ATTACHPTR ***idxp, short *idxlen, short *idxmax,
839 int recv)
840{
841 ATTACHPTR **idx = *idxp;
842#if 0
843 int old_optweed = option (OPTWEED);
844 set_option (OPTWEED);
845#endif
846
847 do
848 {
849 switch (op)
850 {
851 case OP_DISPLAY_HEADERS:
852 toggle_option (OPTWEED);
853 /* fall through */
854
855 case OP_VIEW_ATTACH:
856 op = mutt_view_attachment (fp, idx[menu->current]->content, MUTT_REGULAR,
857 hdr, idx, *idxlen);
858 break;
859
860 case OP_NEXT_ENTRY:
861 case OP_MAIN_NEXT_UNDELETED: /* hack */
862 if (menu->current < menu->max - 1)
863 {
864 menu->current++;
865 op = OP_VIEW_ATTACH;
866 }
867 else
868 op = OP_NULL;
869 break;
870 case OP_PREV_ENTRY:
871 case OP_MAIN_PREV_UNDELETED: /* hack */
872 if (menu->current > 0)
873 {
874 menu->current--;
875 op = OP_VIEW_ATTACH;
876 }
877 else
878 op = OP_NULL;
879 break;
880 case OP_EDIT_TYPE:
881 /* when we edit the content-type, we should redisplay the attachment
882 immediately */
883 mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
884 if (idxmax)
885 {
886 mutt_update_attach_index (cur, idxp, idxlen, idxmax, menu);
887 idx = *idxp;
888 }
889 op = OP_VIEW_ATTACH;
890 break;
891 /* functions which are passed through from the pager */
892 case OP_CHECK_TRADITIONAL:
893 if (!(WithCrypto & APPLICATION_PGP) || (hdr && hdr->security & PGP_TRADITIONAL_CHECKED))
894 {
895 op = OP_NULL;
896 break;
897 }
898 /* fall through */
899 case OP_ATTACH_COLLAPSE:
900 if (recv)
901 return op;
902 default:
903 op = OP_NULL;
904 }
905 }
906 while (op != OP_NULL);
907
908#if 0
909 if (option (OPTWEED) != old_optweed)
910 toggle_option (OPTWEED);
911#endif
912 return op;
913}
914
915static void attach_collapse (BODY *b, short collapse, short init, short just_one)
916{
917 short i;
918 for (; b; b = b->next)
919 {
920 i = init || b->collapsed;
921 if (i && option (OPTDIGESTCOLLAPSE) && b->type == TYPEMULTIPART
922 && !ascii_strcasecmp (b->subtype, "digest"))
923 attach_collapse (b->parts, 1, 1, 0);
924 else if (b->type == TYPEMULTIPART || mutt_is_message_type (b->type, b->subtype))
925 attach_collapse (b->parts, collapse, i, 0);
926 b->collapsed = collapse;
927 if (just_one)
928 return;
929 }
930}
931
932void mutt_attach_init (BODY *b)
933{
934 for (; b; b = b->next)
935 {
936 b->tagged = 0;
937 b->collapsed = 0;
938 if (b->parts)
939 mutt_attach_init (b->parts);
940 }
941}
942
943static const char *Function_not_permitted = N_("Function not permitted in attach-message mode.");
944
945#define CHECK_ATTACH if(option(OPTATTACHMSG)) \
946 {\
947 mutt_flushinp (); \
948 mutt_error _(Function_not_permitted); \
949 break; \
950 }
951
952
953
954
955void mutt_view_attachments (HEADER *hdr)
956{
957 int secured = 0;
958 int need_secured = 0;
959
960 char helpstr[LONG_STRING];
961 MUTTMENU *menu;
962 BODY *cur = NULL;
963 MESSAGE *msg;
964 FILE *fp;
965 ATTACHPTR **idx = NULL;
966 short idxlen = 0;
967 short idxmax = 0;
968 int flags = 0;
969 int op = OP_NULL;
970
971 /* make sure we have parsed this message */
972 mutt_parse_mime_message (Context, hdr);
973
974 mutt_message_hook (Context, hdr, MUTT_MESSAGEHOOK);
975
976 if ((msg = mx_open_message (Context, hdr->msgno)) == NULL)
977 return;
978
979
980 if (WithCrypto && ((hdr->security & ENCRYPT) ||
981 (mutt_is_application_smime(hdr->content) & SMIMEOPAQUE)))
982 {
983 need_secured = 1;
984
985 if ((hdr->security & ENCRYPT) && !crypt_valid_passphrase(hdr->security))
986 {
987 mx_close_message (Context, &msg);
988 return;
989 }
990 if ((WithCrypto & APPLICATION_SMIME) && (hdr->security & APPLICATION_SMIME))
991 {
992 if (hdr->env)
993 crypt_smime_getkeys (hdr->env);
994
995 if (mutt_is_application_smime(hdr->content))
996 {
997 secured = ! crypt_smime_decrypt_mime (msg->fp, &fp,
998 hdr->content, &cur);
999
1000 /* S/MIME nesting */
1001 if ((mutt_is_application_smime (cur) & SMIMEOPAQUE))
1002 {
1003 BODY *_cur = cur;
1004 FILE *_fp = fp;
1005
1006 fp = NULL; cur = NULL;
1007 secured = !crypt_smime_decrypt_mime (_fp, &fp, _cur, &cur);
1008
1009 mutt_free_body (&_cur);
1010 safe_fclose (&_fp);
1011 }
1012 }
1013 else
1014 need_secured = 0;
1015 }
1016 if ((WithCrypto & APPLICATION_PGP) && (hdr->security & APPLICATION_PGP))
1017 {
1018 if (mutt_is_multipart_encrypted(hdr->content) ||
1019 mutt_is_malformed_multipart_pgp_encrypted(hdr->content))
1020 secured = !crypt_pgp_decrypt_mime (msg->fp, &fp, hdr->content, &cur);
1021 else
1022 need_secured = 0;
1023 }
1024
1025 if (need_secured && !secured)
1026 {
1027 mx_close_message (Context, &msg);
1028 mutt_error _("Can't decrypt encrypted message!");
1029 return;
1030 }
1031 }
1032
1033 if (!WithCrypto || !need_secured)
1034 {
1035 fp = msg->fp;
1036 cur = hdr->content;
1037 }
1038
1039 menu = mutt_new_menu (MENU_ATTACH);
1040 menu->title = _("Attachments");
1041 menu->make_entry = attach_entry;
1042 menu->tag = mutt_tag_attach;
1043 menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_ATTACH, AttachHelp);
1044
1045 mutt_attach_init (cur);
1046 attach_collapse (cur, 0, 1, 0);
1047 mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
1048
1049 FOREVER
1050 {
1051 if (op == OP_NULL)
1052 op = mutt_menuLoop (menu);
1053 switch (op)
1054 {
1055 case OP_ATTACH_VIEW_MAILCAP:
1056 mutt_view_attachment (fp, idx[menu->current]->content, MUTT_MAILCAP,
1057 hdr, idx, idxlen);
1058 menu->redraw = REDRAW_FULL;
1059 break;
1060
1061 case OP_ATTACH_VIEW_TEXT:
1062 mutt_view_attachment (fp, idx[menu->current]->content, MUTT_AS_TEXT,
1063 hdr, idx, idxlen);
1064 menu->redraw = REDRAW_FULL;
1065 break;
1066
1067 case OP_DISPLAY_HEADERS:
1068 case OP_VIEW_ATTACH:
1069 op = mutt_attach_display_loop (menu, op, fp, hdr, cur, &idx, &idxlen, &idxmax, 1);
1070 menu->redraw = REDRAW_FULL;
1071 continue;
1072
1073 case OP_ATTACH_COLLAPSE:
1074 if (!idx[menu->current]->content->parts)
1075 {
1076 mutt_error _("There are no subparts to show!");
1077 break;
1078 }
1079 if (!idx[menu->current]->content->collapsed)
1080 attach_collapse (idx[menu->current]->content, 1, 0, 1);
1081 else
1082 attach_collapse (idx[menu->current]->content, 0, 1, 1);
1083 mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
1084 break;
1085
1086 case OP_FORGET_PASSPHRASE:
1087 crypt_forget_passphrase ();
1088 break;
1089
1090 case OP_EXTRACT_KEYS:
1091 if ((WithCrypto & APPLICATION_PGP))
1092 {
1093 crypt_pgp_extract_keys_from_attachment_list (fp, menu->tagprefix,
1094 menu->tagprefix ? cur : idx[menu->current]->content);
1095 menu->redraw = REDRAW_FULL;
1096 }
1097 break;
1098
1099 case OP_CHECK_TRADITIONAL:
1100 if ((WithCrypto & APPLICATION_PGP)
1101 && crypt_pgp_check_traditional (fp, menu->tagprefix ? cur
1102 : idx[menu->current]->content,
1103 menu->tagprefix))
1104 {
1105 hdr->security = crypt_query (cur);
1106 menu->redraw = REDRAW_FULL;
1107 }
1108 break;
1109
1110 case OP_PRINT:
1111 mutt_print_attachment_list (fp, menu->tagprefix,
1112 menu->tagprefix ? cur : idx[menu->current]->content);
1113 break;
1114
1115 case OP_PIPE:
1116 mutt_pipe_attachment_list (fp, menu->tagprefix,
1117 menu->tagprefix ? cur : idx[menu->current]->content, 0);
1118 break;
1119
1120 case OP_SAVE:
1121 mutt_save_attachment_list (fp, menu->tagprefix,
1122 menu->tagprefix ? cur : idx[menu->current]->content, hdr, menu);
1123
1124 if (!menu->tagprefix && option (OPTRESOLVE) && menu->current < menu->max - 1)
1125 menu->current++;
1126
1127 menu->redraw = REDRAW_MOTION_RESYNCH | REDRAW_FULL;
1128 break;
1129
1130 case OP_DELETE:
1131 CHECK_READONLY;
1132
1133#ifdef USE_POP
1134 if (Context->magic == MUTT_POP)
1135 {
1136 mutt_flushinp ();
1137 mutt_error _("Can't delete attachment from POP server.");
1138 break;
1139 }
1140#endif
1141
1142 if (WithCrypto && (hdr->security & ENCRYPT))
1143 {
1144 mutt_message _(
1145 "Deletion of attachments from encrypted messages is unsupported.");
1146 break;
1147 }
1148 if (WithCrypto && (hdr->security & (SIGN | PARTSIGN)))
1149 {
1150 mutt_message _(
1151 "Deletion of attachments from signed messages may invalidate the signature.");
1152 }
1153 if (!menu->tagprefix)
1154 {
1155 if (idx[menu->current]->parent_type == TYPEMULTIPART)
1156 {
1157 idx[menu->current]->content->deleted = 1;
1158 if (option (OPTRESOLVE) && menu->current < menu->max - 1)
1159 {
1160 menu->current++;
1161 menu->redraw = REDRAW_MOTION_RESYNCH;
1162 }
1163 else
1164 menu->redraw = REDRAW_CURRENT;
1165 }
1166 else
1167 mutt_message _(
1168 "Only deletion of multipart attachments is supported.");
1169 }
1170 else
1171 {
1172 int x;
1173
1174 for (x = 0; x < menu->max; x++)
1175 {
1176 if (idx[x]->content->tagged)
1177 {
1178 if (idx[x]->parent_type == TYPEMULTIPART)
1179 {
1180 idx[x]->content->deleted = 1;
1181 menu->redraw = REDRAW_INDEX;
1182 }
1183 else
1184 mutt_message _(
1185 "Only deletion of multipart attachments is supported.");
1186 }
1187 }
1188 }
1189 break;
1190
1191 case OP_UNDELETE:
1192 CHECK_READONLY;
1193 if (!menu->tagprefix)
1194 {
1195 idx[menu->current]->content->deleted = 0;
1196 if (option (OPTRESOLVE) && menu->current < menu->max - 1)
1197 {
1198 menu->current++;
1199 menu->redraw = REDRAW_MOTION_RESYNCH;
1200 }
1201 else
1202 menu->redraw = REDRAW_CURRENT;
1203 }
1204 else
1205 {
1206 int x;
1207
1208 for (x = 0; x < menu->max; x++)
1209 {
1210 if (idx[x]->content->tagged)
1211 {
1212 idx[x]->content->deleted = 0;
1213 menu->redraw = REDRAW_INDEX;
1214 }
1215 }
1216 }
1217 break;
1218
1219 case OP_RESEND:
1220 CHECK_ATTACH;
1221 mutt_attach_resend (fp, hdr, idx, idxlen,
1222 menu->tagprefix ? NULL : idx[menu->current]->content);
1223 menu->redraw = REDRAW_FULL;
1224 break;
1225
1226 case OP_BOUNCE_MESSAGE:
1227 CHECK_ATTACH;
1228 mutt_attach_bounce (fp, hdr, idx, idxlen,
1229 menu->tagprefix ? NULL : idx[menu->current]->content);
1230 menu->redraw = REDRAW_FULL;
1231 break;
1232
1233 case OP_FORWARD_MESSAGE:
1234 CHECK_ATTACH;
1235 mutt_attach_forward (fp, hdr, idx, idxlen,
1236 menu->tagprefix ? NULL : idx[menu->current]->content);
1237 menu->redraw = REDRAW_FULL;
1238 break;
1239
1240 case OP_REPLY:
1241 case OP_GROUP_REPLY:
1242 case OP_LIST_REPLY:
1243
1244 CHECK_ATTACH;
1245
1246 flags = SENDREPLY |
1247 (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) |
1248 (op == OP_LIST_REPLY ? SENDLISTREPLY : 0);
1249 mutt_attach_reply (fp, hdr, idx, idxlen,
1250 menu->tagprefix ? NULL : idx[menu->current]->content, flags);
1251 menu->redraw = REDRAW_FULL;
1252 break;
1253
1254 case OP_EDIT_TYPE:
1255 mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
1256 mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
1257 break;
1258
1259 case OP_EXIT:
1260 mx_close_message (Context, &msg);
1261 hdr->attach_del = 0;
1262 while (idxmax-- > 0)
1263 {
1264 if (!idx[idxmax])
1265 continue;
1266 if (idx[idxmax]->content && idx[idxmax]->content->deleted)
1267 hdr->attach_del = 1;
1268 if (idx[idxmax]->content)
1269 idx[idxmax]->content->aptr = NULL;
1270 FREE (&idx[idxmax]->tree);
1271 FREE (&idx[idxmax]);
1272 }
1273 if (hdr->attach_del)
1274 hdr->changed = 1;
1275 FREE (&idx);
1276 idxmax = 0;
1277
1278 if (WithCrypto && need_secured && secured)
1279 {
1280 safe_fclose (&fp);
1281 mutt_free_body (&cur);
1282 }
1283
1284 mutt_menuDestroy (&menu);
1285 return;
1286 }
1287
1288 op = OP_NULL;
1289 }
1290
1291 /* not reached */
1292}