PageRenderTime 20ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/recvattach.c

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