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