Coverage Report

Created: 2023-09-25 07:17

/src/neomutt/attach/mutt_attach.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Handling of email attachments
4
 *
5
 * @authors
6
 * Copyright (C) 1996-2000,2002,2013 Michael R. Elkins <me@mutt.org>
7
 * Copyright (C) 1999-2004,2006 Thomas Roessler <roessler@does-not-exist.org>
8
 *
9
 * @copyright
10
 * This program is free software: you can redistribute it and/or modify it under
11
 * the terms of the GNU General Public License as published by the Free Software
12
 * Foundation, either version 2 of the License, or (at your option) any later
13
 * version.
14
 *
15
 * This program is distributed in the hope that it will be useful, but WITHOUT
16
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18
 * details.
19
 *
20
 * You should have received a copy of the GNU General Public License along with
21
 * this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
/**
25
 * @page attach_mutt_attach Shared attachments functions
26
 *
27
 * Handling of email attachments
28
 */
29
30
#include "config.h"
31
#include <errno.h>
32
#include <fcntl.h>
33
#include <stdbool.h>
34
#include <stdio.h>
35
#include <string.h>
36
#include <sys/stat.h>
37
#include <sys/wait.h>
38
#include <unistd.h>
39
#include "mutt/lib.h"
40
#include "config/lib.h"
41
#include "email/lib.h"
42
#include "core/lib.h"
43
#include "gui/lib.h"
44
#include "mutt_attach.h"
45
#include "lib.h"
46
#include "ncrypt/lib.h"
47
#include "pager/lib.h"
48
#include "question/lib.h"
49
#include "send/lib.h"
50
#include "attach.h"
51
#include "cid.h"
52
#include "copy.h"
53
#include "globals.h" // IWYU pragma: keep
54
#include "handler.h"
55
#include "mailcap.h"
56
#include "muttlib.h"
57
#include "mx.h"
58
#include "protos.h"
59
#include "rfc3676.h"
60
#ifdef USE_IMAP
61
#include "imap/lib.h"
62
#endif
63
64
/**
65
 * mutt_get_tmp_attachment - Get a temporary copy of an attachment
66
 * @param a Attachment to copy
67
 * @retval  0 Success
68
 * @retval -1 Error
69
 */
70
int mutt_get_tmp_attachment(struct Body *a)
71
0
{
72
0
  char type[256] = { 0 };
73
74
0
  if (a->unlink)
75
0
    return 0;
76
77
0
  struct Buffer *tmpfile = buf_pool_get();
78
0
  struct MailcapEntry *entry = mailcap_entry_new();
79
0
  snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
80
0
  mailcap_lookup(a, type, sizeof(type), entry, MUTT_MC_NO_FLAGS);
81
0
  mailcap_expand_filename(entry->nametemplate, a->filename, tmpfile);
82
83
0
  mailcap_entry_free(&entry);
84
85
0
  FILE *fp_in = NULL, *fp_out = NULL;
86
0
  if ((fp_in = fopen(a->filename, "r")) &&
87
0
      (fp_out = mutt_file_fopen(buf_string(tmpfile), "w")))
88
0
  {
89
0
    mutt_file_copy_stream(fp_in, fp_out);
90
0
    mutt_str_replace(&a->filename, buf_string(tmpfile));
91
0
    a->unlink = true;
92
93
0
    struct stat st = { 0 };
94
0
    if ((fstat(fileno(fp_in), &st) == 0) && (a->stamp >= st.st_mtime))
95
0
    {
96
0
      mutt_stamp_attachment(a);
97
0
    }
98
0
  }
99
0
  else
100
0
  {
101
0
    mutt_perror("%s", fp_in ? buf_string(tmpfile) : a->filename);
102
0
  }
103
104
0
  mutt_file_fclose(&fp_in);
105
0
  mutt_file_fclose(&fp_out);
106
107
0
  buf_pool_release(&tmpfile);
108
109
0
  return a->unlink ? 0 : -1;
110
0
}
111
112
/**
113
 * mutt_compose_attachment - Create an attachment
114
 * @param a Body of email
115
 * @retval 1 Require full screen redraw
116
 * @retval 0 Otherwise
117
 */
118
int mutt_compose_attachment(struct Body *a)
119
0
{
120
0
  char type[256] = { 0 };
121
0
  struct MailcapEntry *entry = mailcap_entry_new();
122
0
  bool unlink_newfile = false;
123
0
  int rc = 0;
124
0
  struct Buffer *cmd = buf_pool_get();
125
0
  struct Buffer *newfile = buf_pool_get();
126
0
  struct Buffer *tmpfile = buf_pool_get();
127
128
0
  snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
129
0
  if (mailcap_lookup(a, type, sizeof(type), entry, MUTT_MC_COMPOSE))
130
0
  {
131
0
    if (entry->composecommand || entry->composetypecommand)
132
0
    {
133
0
      if (entry->composetypecommand)
134
0
        buf_strcpy(cmd, entry->composetypecommand);
135
0
      else
136
0
        buf_strcpy(cmd, entry->composecommand);
137
138
0
      mailcap_expand_filename(entry->nametemplate, a->filename, newfile);
139
0
      mutt_debug(LL_DEBUG1, "oldfile: %s     newfile: %s\n", a->filename,
140
0
                 buf_string(newfile));
141
0
      if (mutt_file_symlink(a->filename, buf_string(newfile)) == -1)
142
0
      {
143
0
        if (query_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES)
144
0
          goto bailout;
145
0
        buf_strcpy(newfile, a->filename);
146
0
      }
147
0
      else
148
0
      {
149
0
        unlink_newfile = true;
150
0
      }
151
152
0
      if (mailcap_expand_command(a, buf_string(newfile), type, cmd))
153
0
      {
154
        /* For now, editing requires a file, no piping */
155
0
        mutt_error(_("Mailcap compose entry requires %%s"));
156
0
      }
157
0
      else
158
0
      {
159
0
        int r;
160
161
0
        mutt_endwin();
162
0
        r = mutt_system(buf_string(cmd));
163
0
        if (r == -1)
164
0
          mutt_error(_("Error running \"%s\""), buf_string(cmd));
165
166
0
        if ((r != -1) && entry->composetypecommand)
167
0
        {
168
0
          struct Body *b = NULL;
169
170
0
          FILE *fp = mutt_file_fopen(a->filename, "r");
171
0
          if (!fp)
172
0
          {
173
0
            mutt_perror(_("Failure to open file to parse headers"));
174
0
            goto bailout;
175
0
          }
176
177
0
          b = mutt_read_mime_header(fp, 0);
178
0
          if (b)
179
0
          {
180
0
            if (!TAILQ_EMPTY(&b->parameter))
181
0
            {
182
0
              mutt_param_free(&a->parameter);
183
0
              a->parameter = b->parameter;
184
0
              TAILQ_INIT(&b->parameter);
185
0
            }
186
0
            if (b->description)
187
0
            {
188
0
              FREE(&a->description);
189
0
              a->description = b->description;
190
0
              b->description = NULL;
191
0
            }
192
0
            if (b->form_name)
193
0
            {
194
0
              FREE(&a->form_name);
195
0
              a->form_name = b->form_name;
196
0
              b->form_name = NULL;
197
0
            }
198
199
            /* Remove headers by copying out data to another file, then
200
             * copying the file back */
201
0
            const LOFF_T offset = b->offset;
202
0
            mutt_body_free(&b);
203
0
            if (!mutt_file_seek(fp, offset, SEEK_SET))
204
0
            {
205
0
              goto bailout;
206
0
            }
207
208
0
            buf_mktemp(tmpfile);
209
0
            FILE *fp_tmp = mutt_file_fopen(buf_string(tmpfile), "w");
210
0
            if (!fp_tmp)
211
0
            {
212
0
              mutt_perror(_("Failure to open file to strip headers"));
213
0
              mutt_file_fclose(&fp);
214
0
              goto bailout;
215
0
            }
216
0
            mutt_file_copy_stream(fp, fp_tmp);
217
0
            mutt_file_fclose(&fp);
218
0
            mutt_file_fclose(&fp_tmp);
219
0
            mutt_file_unlink(a->filename);
220
0
            if (mutt_file_rename(buf_string(tmpfile), a->filename) != 0)
221
0
            {
222
0
              mutt_perror(_("Failure to rename file"));
223
0
              goto bailout;
224
0
            }
225
0
          }
226
0
        }
227
0
      }
228
0
    }
229
0
  }
230
0
  else
231
0
  {
232
0
    mutt_message(_("No mailcap compose entry for %s, creating empty file"), type);
233
0
    rc = 1;
234
0
    goto bailout;
235
0
  }
236
237
0
  rc = 1;
238
239
0
bailout:
240
241
0
  if (unlink_newfile)
242
0
    unlink(buf_string(newfile));
243
244
0
  buf_pool_release(&cmd);
245
0
  buf_pool_release(&newfile);
246
0
  buf_pool_release(&tmpfile);
247
248
0
  mailcap_entry_free(&entry);
249
0
  return rc;
250
0
}
251
252
/**
253
 * mutt_edit_attachment - Edit an attachment
254
 * @param a Email containing attachment
255
 * @retval true  Editor found
256
 * @retval false Editor not found
257
 *
258
 * Currently, this only works for send mode, as it assumes that the
259
 * Body->filename actually contains the information.  I'm not sure
260
 * we want to deal with editing attachments we've already received,
261
 * so this should be ok.
262
 *
263
 * Returning 0 is useful to tell the calling menu to redraw
264
 */
265
bool mutt_edit_attachment(struct Body *a)
266
0
{
267
0
  char type[256] = { 0 };
268
0
  struct MailcapEntry *entry = mailcap_entry_new();
269
0
  bool unlink_newfile = false;
270
0
  bool rc = false;
271
0
  struct Buffer *cmd = buf_pool_get();
272
0
  struct Buffer *newfile = buf_pool_get();
273
274
0
  snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
275
0
  if (mailcap_lookup(a, type, sizeof(type), entry, MUTT_MC_EDIT))
276
0
  {
277
0
    if (entry->editcommand)
278
0
    {
279
0
      buf_strcpy(cmd, entry->editcommand);
280
0
      mailcap_expand_filename(entry->nametemplate, a->filename, newfile);
281
0
      mutt_debug(LL_DEBUG1, "oldfile: %s     newfile: %s\n", a->filename,
282
0
                 buf_string(newfile));
283
0
      if (mutt_file_symlink(a->filename, buf_string(newfile)) == -1)
284
0
      {
285
0
        if (query_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES)
286
0
          goto bailout;
287
0
        buf_strcpy(newfile, a->filename);
288
0
      }
289
0
      else
290
0
      {
291
0
        unlink_newfile = true;
292
0
      }
293
294
0
      if (mailcap_expand_command(a, buf_string(newfile), type, cmd))
295
0
      {
296
        /* For now, editing requires a file, no piping */
297
0
        mutt_error(_("Mailcap Edit entry requires %%s"));
298
0
        goto bailout;
299
0
      }
300
0
      else
301
0
      {
302
0
        mutt_endwin();
303
0
        if (mutt_system(buf_string(cmd)) == -1)
304
0
        {
305
0
          mutt_error(_("Error running \"%s\""), buf_string(cmd));
306
0
          goto bailout;
307
0
        }
308
0
      }
309
0
    }
310
0
  }
311
0
  else if (a->type == TYPE_TEXT)
312
0
  {
313
    /* On text, default to editor */
314
0
    const char *const c_editor = cs_subset_string(NeoMutt->sub, "editor");
315
0
    mutt_edit_file(NONULL(c_editor), a->filename);
316
0
  }
317
0
  else
318
0
  {
319
0
    mutt_error(_("No mailcap edit entry for %s"), type);
320
0
    goto bailout;
321
0
  }
322
323
0
  rc = true;
324
325
0
bailout:
326
327
0
  if (unlink_newfile)
328
0
    unlink(buf_string(newfile));
329
330
0
  buf_pool_release(&cmd);
331
0
  buf_pool_release(&newfile);
332
333
0
  mailcap_entry_free(&entry);
334
0
  return rc;
335
0
}
336
337
/**
338
 * mutt_check_lookup_list - Update the mime type
339
 * @param b    Message attachment body
340
 * @param type Buffer with mime type of attachment in "type/subtype" format
341
 * @param len  Buffer length
342
 */
343
void mutt_check_lookup_list(struct Body *b, char *type, size_t len)
344
0
{
345
0
  struct ListNode *np = NULL;
346
0
  STAILQ_FOREACH(np, &MimeLookupList, entries)
347
0
  {
348
0
    const int i = mutt_str_len(np->data) - 1;
349
0
    if (((i > 0) && (np->data[i - 1] == '/') && (np->data[i] == '*') &&
350
0
         mutt_istrn_equal(type, np->data, i)) ||
351
0
        mutt_istr_equal(type, np->data))
352
0
    {
353
0
      struct Body tmp = { 0 };
354
0
      enum ContentType n;
355
0
      if ((n = mutt_lookup_mime_type(&tmp, b->filename)) != TYPE_OTHER ||
356
0
          (n = mutt_lookup_mime_type(&tmp, b->description)) != TYPE_OTHER)
357
0
      {
358
0
        snprintf(type, len, "%s/%s",
359
0
                 (n == TYPE_AUDIO)       ? "audio" :
360
0
                 (n == TYPE_APPLICATION) ? "application" :
361
0
                 (n == TYPE_IMAGE)       ? "image" :
362
0
                 (n == TYPE_MESSAGE)     ? "message" :
363
0
                 (n == TYPE_MODEL)       ? "model" :
364
0
                 (n == TYPE_MULTIPART)   ? "multipart" :
365
0
                 (n == TYPE_TEXT)        ? "text" :
366
0
                 (n == TYPE_VIDEO)       ? "video" :
367
0
                                           "other",
368
0
                 tmp.subtype);
369
0
        mutt_debug(LL_DEBUG1, "\"%s\" -> %s\n", b->filename, type);
370
0
      }
371
0
      FREE(&tmp.subtype);
372
0
      FREE(&tmp.xtype);
373
0
    }
374
0
  }
375
0
}
376
377
/**
378
 * wait_interactive_filter - Wait after an interactive filter
379
 * @param pid Process id of the process to wait for
380
 * @retval num Exit status of the process identified by pid
381
 * @retval -1  Error
382
 *
383
 * This is used for filters that are actually interactive commands
384
 * with input piped in: e.g. in mutt_view_attachment(), a mailcap
385
 * entry without copiousoutput _and_ without a %s.
386
 *
387
 * For those cases, we treat it like a blocking system command, and
388
 * poll IMAP to keep connections open.
389
 */
390
static int wait_interactive_filter(pid_t pid)
391
0
{
392
0
  int rc;
393
394
0
#ifdef USE_IMAP
395
0
  rc = imap_wait_keep_alive(pid);
396
#else
397
  waitpid(pid, &rc, 0);
398
#endif
399
0
  mutt_sig_unblock_system(true);
400
0
  rc = WIFEXITED(rc) ? WEXITSTATUS(rc) : -1;
401
402
0
  return rc;
403
0
}
404
405
/**
406
 * mutt_view_attachment - View an attachment
407
 * @param fp     Source file stream. Can be NULL
408
 * @param a      The message body containing the attachment
409
 * @param mode   How the attachment should be viewed, see #ViewAttachMode
410
 * @param e      Current Email. Can be NULL
411
 * @param actx   Attachment context
412
 * @param win    Window
413
 * @retval 0   The viewer is run and exited successfully
414
 * @retval -1  Error
415
 * @retval num Return value of mutt_do_pager() when it is used
416
 *
417
 * Display a message attachment using the viewer program configured in mailcap.
418
 * If there is no mailcap entry for a file type, view the image as text.
419
 * Viewer processes are opened and waited on synchronously so viewing an
420
 * attachment this way will block the main neomutt process until the viewer process
421
 * exits.
422
 */
423
int mutt_view_attachment(FILE *fp, struct Body *a, enum ViewAttachMode mode,
424
                         struct Email *e, struct AttachCtx *actx, struct MuttWindow *win)
425
0
{
426
0
  bool use_mailcap = false;
427
0
  bool use_pipe = false;
428
0
  bool use_pager = true;
429
0
  char type[256] = { 0 };
430
0
  char desc[256] = { 0 };
431
0
  char *fname = NULL;
432
0
  struct MailcapEntry *entry = NULL;
433
0
  int rc = -1;
434
0
  bool has_tempfile = false;
435
0
  bool unlink_pagerfile = false;
436
437
0
  bool is_message = mutt_is_message_type(a->type, a->subtype);
438
0
  if ((WithCrypto != 0) && is_message && a->email &&
439
0
      (a->email->security & SEC_ENCRYPT) && !crypt_valid_passphrase(a->email->security))
440
0
  {
441
0
    return rc;
442
0
  }
443
444
0
  struct Buffer *tmpfile = buf_pool_get();
445
0
  struct Buffer *pagerfile = buf_pool_get();
446
0
  struct Buffer *cmd = buf_pool_get();
447
448
0
  use_mailcap = ((mode == MUTT_VA_MAILCAP) ||
449
0
                 ((mode == MUTT_VA_REGULAR) && mutt_needs_mailcap(a)) ||
450
0
                 (mode == MUTT_VA_PAGER));
451
0
  snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
452
453
0
  char columns[16] = { 0 };
454
0
  snprintf(columns, sizeof(columns), "%d", win->state.cols);
455
0
  envlist_set(&EnvList, "COLUMNS", columns, true);
456
457
0
  if (use_mailcap)
458
0
  {
459
0
    entry = mailcap_entry_new();
460
0
    enum MailcapLookup mailcap_opt = (mode == MUTT_VA_PAGER) ? MUTT_MC_AUTOVIEW : MUTT_MC_NO_FLAGS;
461
0
    if (!mailcap_lookup(a, type, sizeof(type), entry, mailcap_opt))
462
0
    {
463
0
      if ((mode == MUTT_VA_REGULAR) || (mode == MUTT_VA_PAGER))
464
0
      {
465
        /* fallback to view as text */
466
0
        mailcap_entry_free(&entry);
467
0
        mutt_error(_("No matching mailcap entry found.  Viewing as text."));
468
0
        mode = MUTT_VA_AS_TEXT;
469
0
        use_mailcap = false;
470
0
      }
471
0
      else
472
0
      {
473
0
        goto return_error;
474
0
      }
475
0
    }
476
0
  }
477
478
0
  if (use_mailcap)
479
0
  {
480
0
    if (!entry->command)
481
0
    {
482
0
      mutt_error(_("MIME type not defined.  Can't view attachment."));
483
0
      goto return_error;
484
0
    }
485
0
    buf_strcpy(cmd, entry->command);
486
487
0
    fname = mutt_str_dup(a->filename);
488
    /* In send mode(!fp), we allow slashes because those are part of
489
     * the tmpfile.  The path will be removed in expand_filename */
490
0
    mutt_file_sanitize_filename(fname, fp ? true : false);
491
0
    mailcap_expand_filename(entry->nametemplate, fname, tmpfile);
492
0
    FREE(&fname);
493
494
0
    if (mutt_save_attachment(fp, a, buf_string(tmpfile), 0, NULL) == -1)
495
0
      goto return_error;
496
0
    has_tempfile = true;
497
498
0
    mutt_rfc3676_space_unstuff_attachment(a, buf_string(tmpfile));
499
500
    /* check for multipart/related and save attachments with a Content-ID */
501
0
    if (mutt_str_equal(type, "text/html"))
502
0
    {
503
0
      struct Body *related_ancestor = NULL;
504
0
      if (actx->body_idx && (WithCrypto != 0) && (e->security & SEC_ENCRYPT))
505
0
        related_ancestor = attach_body_ancestor(actx->body_idx[0], a, "related");
506
0
      else
507
0
        related_ancestor = attach_body_ancestor(e->body, a, "related");
508
0
      if (related_ancestor)
509
0
      {
510
0
        struct CidMapList cid_map_list = STAILQ_HEAD_INITIALIZER(cid_map_list);
511
0
        mutt_debug(LL_DEBUG2, "viewing text/html attachment in multipart/related group\n");
512
        /* save attachments and build cid_map_list Content-ID to filename mapping list */
513
0
        cid_save_attachments(related_ancestor->parts, &cid_map_list);
514
        /* replace Content-IDs with filenames */
515
0
        cid_to_filename(tmpfile, &cid_map_list);
516
        /* empty Content-ID to filename mapping list */
517
0
        cid_map_list_clear(&cid_map_list);
518
0
      }
519
0
    }
520
521
0
    use_pipe = mailcap_expand_command(a, buf_string(tmpfile), type, cmd);
522
0
    use_pager = entry->copiousoutput;
523
0
  }
524
525
0
  if (use_pager)
526
0
  {
527
0
    if (fp && !use_mailcap && a->filename)
528
0
    {
529
      /* recv case */
530
0
      buf_strcpy(pagerfile, a->filename);
531
0
      mutt_adv_mktemp(pagerfile);
532
0
    }
533
0
    else
534
0
    {
535
0
      buf_mktemp(pagerfile);
536
0
    }
537
0
  }
538
539
0
  if (use_mailcap)
540
0
  {
541
0
    pid_t pid = 0;
542
0
    int fd_temp = -1, fd_pager = -1;
543
544
0
    if (!use_pager)
545
0
      mutt_endwin();
546
547
0
    const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
548
0
    if (use_pager || use_pipe)
549
0
    {
550
0
      if (use_pager && ((fd_pager = mutt_file_open(buf_string(pagerfile),
551
0
                                                   O_CREAT | O_EXCL | O_WRONLY)) == -1))
552
0
      {
553
0
        mutt_perror("open");
554
0
        goto return_error;
555
0
      }
556
0
      unlink_pagerfile = true;
557
558
0
      if (use_pipe && ((fd_temp = open(buf_string(tmpfile), 0)) == -1))
559
0
      {
560
0
        if (fd_pager != -1)
561
0
          close(fd_pager);
562
0
        mutt_perror("open");
563
0
        goto return_error;
564
0
      }
565
0
      unlink_pagerfile = true;
566
567
0
      pid = filter_create_fd(buf_string(cmd), NULL, NULL, NULL, use_pipe ? fd_temp : -1,
568
0
                             use_pager ? fd_pager : -1, -1, EnvList);
569
570
0
      if (pid == -1)
571
0
      {
572
0
        if (fd_pager != -1)
573
0
          close(fd_pager);
574
575
0
        if (fd_temp != -1)
576
0
          close(fd_temp);
577
578
0
        mutt_error(_("Can't create filter"));
579
0
        goto return_error;
580
0
      }
581
582
0
      if (use_pager)
583
0
      {
584
0
        if (a->description)
585
0
        {
586
0
          snprintf(desc, sizeof(desc), _("---Command: %-20.20s Description: %s"),
587
0
                   buf_string(cmd), a->description);
588
0
        }
589
0
        else
590
0
        {
591
0
          snprintf(desc, sizeof(desc), _("---Command: %-30.30s Attachment: %s"),
592
0
                   buf_string(cmd), type);
593
0
        }
594
0
        filter_wait(pid);
595
0
      }
596
0
      else
597
0
      {
598
0
        if (wait_interactive_filter(pid) || (entry->needsterminal && c_wait_key))
599
0
          mutt_any_key_to_continue(NULL);
600
0
      }
601
602
0
      if (fd_temp != -1)
603
0
        close(fd_temp);
604
0
      if (fd_pager != -1)
605
0
        close(fd_pager);
606
0
    }
607
0
    else
608
0
    {
609
      /* interactive cmd */
610
0
      int rv = mutt_system(buf_string(cmd));
611
0
      if (rv == -1)
612
0
        mutt_debug(LL_DEBUG1, "Error running \"%s\"\n", cmd->data);
613
614
0
      if ((rv != 0) || (entry->needsterminal && c_wait_key))
615
0
        mutt_any_key_to_continue(NULL);
616
0
    }
617
0
  }
618
0
  else
619
0
  {
620
    /* Don't use mailcap; the attachment is viewed in the pager */
621
622
0
    if (mode == MUTT_VA_AS_TEXT)
623
0
    {
624
      /* just let me see the raw data */
625
0
      if (fp)
626
0
      {
627
        /* Viewing from a received message.
628
         *
629
         * Don't use mutt_save_attachment() because we want to perform charset
630
         * conversion since this will be displayed by the internal pager.  */
631
0
        struct State state = { 0 };
632
633
0
        state.fp_out = mutt_file_fopen(buf_string(pagerfile), "w");
634
0
        if (!state.fp_out)
635
0
        {
636
0
          mutt_debug(LL_DEBUG1, "mutt_file_fopen(%s) errno=%d %s\n",
637
0
                     buf_string(pagerfile), errno, strerror(errno));
638
0
          mutt_perror("%s", buf_string(pagerfile));
639
0
          goto return_error;
640
0
        }
641
0
        state.fp_in = fp;
642
0
        state.flags = STATE_CHARCONV;
643
0
        mutt_decode_attachment(a, &state);
644
0
        if (mutt_file_fclose(&state.fp_out) == EOF)
645
0
        {
646
0
          mutt_debug(LL_DEBUG1, "fclose(%s) errno=%d %s\n",
647
0
                     buf_string(pagerfile), errno, strerror(errno));
648
0
        }
649
0
      }
650
0
      else
651
0
      {
652
        /* in compose mode, just copy the file.  we can't use
653
         * mutt_decode_attachment() since it assumes the content-encoding has
654
         * already been applied */
655
0
        if (mutt_save_attachment(fp, a, buf_string(pagerfile), MUTT_SAVE_NO_FLAGS, NULL))
656
0
          goto return_error;
657
0
        unlink_pagerfile = true;
658
0
      }
659
0
      mutt_rfc3676_space_unstuff_attachment(a, buf_string(pagerfile));
660
0
    }
661
0
    else
662
0
    {
663
0
      StateFlags flags = STATE_DISPLAY | STATE_DISPLAY_ATTACH;
664
0
      const char *const c_pager = pager_get_pager(NeoMutt->sub);
665
0
      if (!c_pager)
666
0
        flags |= STATE_PAGER;
667
668
      /* Use built-in handler */
669
0
      if (mutt_decode_save_attachment(fp, a, buf_string(pagerfile), flags, MUTT_SAVE_NO_FLAGS))
670
0
      {
671
0
        goto return_error;
672
0
      }
673
0
      unlink_pagerfile = true;
674
0
    }
675
676
0
    if (a->description)
677
0
      mutt_str_copy(desc, a->description, sizeof(desc));
678
0
    else if (a->filename)
679
0
      snprintf(desc, sizeof(desc), _("---Attachment: %s: %s"), a->filename, type);
680
0
    else
681
0
      snprintf(desc, sizeof(desc), _("---Attachment: %s"), type);
682
0
  }
683
684
  /* We only reach this point if there have been no errors */
685
686
0
  if (use_pager)
687
0
  {
688
0
    struct PagerData pdata = { 0 };
689
0
    struct PagerView pview = { &pdata };
690
691
0
    pdata.actx = actx;
692
0
    pdata.body = a;
693
0
    pdata.fname = buf_string(pagerfile);
694
0
    pdata.fp = fp;
695
696
0
    pview.banner = desc;
697
0
    pview.flags = MUTT_PAGER_ATTACHMENT |
698
0
                  (is_message ? MUTT_PAGER_MESSAGE : MUTT_PAGER_NO_FLAGS) |
699
0
                  ((use_mailcap && entry->xneomuttnowrap) ? MUTT_PAGER_NOWRAP :
700
0
                                                            MUTT_PAGER_NO_FLAGS);
701
0
    pview.mode = PAGER_MODE_ATTACH;
702
703
0
    rc = mutt_do_pager(&pview, e);
704
705
0
    buf_reset(pagerfile);
706
0
    unlink_pagerfile = false;
707
0
  }
708
0
  else
709
0
  {
710
0
    rc = 0;
711
0
  }
712
713
0
return_error:
714
715
0
  if (!entry || !entry->xneomuttkeep)
716
0
  {
717
0
    if ((fp && !buf_is_empty(tmpfile)) || has_tempfile)
718
0
    {
719
      /* add temporary file to TempAttachmentsList to be deleted on timeout hook */
720
0
      mutt_add_temp_attachment(buf_string(tmpfile));
721
0
    }
722
0
  }
723
724
0
  mailcap_entry_free(&entry);
725
726
0
  if (unlink_pagerfile)
727
0
    mutt_file_unlink(buf_string(pagerfile));
728
729
0
  buf_pool_release(&tmpfile);
730
0
  buf_pool_release(&pagerfile);
731
0
  buf_pool_release(&cmd);
732
0
  envlist_unset(&EnvList, "COLUMNS");
733
734
0
  return rc;
735
0
}
736
737
/**
738
 * mutt_pipe_attachment - Pipe an attachment to a command
739
 * @param fp      File to pipe into the command
740
 * @param b       Attachment
741
 * @param path    Path to command
742
 * @param outfile File to save output to
743
 * @retval 1 Success
744
 * @retval 0 Error
745
 */
746
int mutt_pipe_attachment(FILE *fp, struct Body *b, const char *path, const char *outfile)
747
0
{
748
0
  pid_t pid = 0;
749
0
  int out = -1, rc = 0;
750
0
  bool is_flowed = false;
751
0
  bool unlink_unstuff = false;
752
0
  FILE *fp_filter = NULL, *fp_unstuff = NULL, *fp_in = NULL;
753
0
  struct Buffer *unstuff_tempfile = NULL;
754
755
0
  if (outfile && *outfile)
756
0
  {
757
0
    out = mutt_file_open(outfile, O_CREAT | O_EXCL | O_WRONLY);
758
0
    if (out < 0)
759
0
    {
760
0
      mutt_perror("open");
761
0
      return 0;
762
0
    }
763
0
  }
764
765
0
  if (mutt_rfc3676_is_format_flowed(b))
766
0
  {
767
0
    is_flowed = true;
768
0
    unstuff_tempfile = buf_pool_get();
769
0
    buf_mktemp(unstuff_tempfile);
770
0
  }
771
772
0
  mutt_endwin();
773
774
0
  if (outfile && *outfile)
775
0
    pid = filter_create_fd(path, &fp_filter, NULL, NULL, -1, out, -1, EnvList);
776
0
  else
777
0
    pid = filter_create(path, &fp_filter, NULL, NULL, EnvList);
778
0
  if (pid < 0)
779
0
  {
780
0
    mutt_perror(_("Can't create filter"));
781
0
    goto bail;
782
0
  }
783
784
  /* recv case */
785
0
  if (fp)
786
0
  {
787
0
    struct State state = { 0 };
788
789
    /* perform charset conversion on text attachments when piping */
790
0
    state.flags = STATE_CHARCONV;
791
792
0
    if (is_flowed)
793
0
    {
794
0
      fp_unstuff = mutt_file_fopen(buf_string(unstuff_tempfile), "w");
795
0
      if (!fp_unstuff)
796
0
      {
797
0
        mutt_perror("mutt_file_fopen");
798
0
        goto bail;
799
0
      }
800
0
      unlink_unstuff = true;
801
802
0
      state.fp_in = fp;
803
0
      state.fp_out = fp_unstuff;
804
0
      mutt_decode_attachment(b, &state);
805
0
      mutt_file_fclose(&fp_unstuff);
806
807
0
      mutt_rfc3676_space_unstuff_attachment(b, buf_string(unstuff_tempfile));
808
809
0
      fp_unstuff = mutt_file_fopen(buf_string(unstuff_tempfile), "r");
810
0
      if (!fp_unstuff)
811
0
      {
812
0
        mutt_perror("mutt_file_fopen");
813
0
        goto bail;
814
0
      }
815
0
      mutt_file_copy_stream(fp_unstuff, fp_filter);
816
0
      mutt_file_fclose(&fp_unstuff);
817
0
    }
818
0
    else
819
0
    {
820
0
      state.fp_in = fp;
821
0
      state.fp_out = fp_filter;
822
0
      mutt_decode_attachment(b, &state);
823
0
    }
824
0
  }
825
0
  else
826
0
  {
827
    /* send case */
828
0
    const char *infile = NULL;
829
830
0
    if (is_flowed)
831
0
    {
832
0
      if (mutt_save_attachment(fp, b, buf_string(unstuff_tempfile),
833
0
                               MUTT_SAVE_NO_FLAGS, NULL) == -1)
834
0
      {
835
0
        goto bail;
836
0
      }
837
0
      unlink_unstuff = true;
838
0
      mutt_rfc3676_space_unstuff_attachment(b, buf_string(unstuff_tempfile));
839
0
      infile = buf_string(unstuff_tempfile);
840
0
    }
841
0
    else
842
0
    {
843
0
      infile = b->filename;
844
0
    }
845
846
0
    fp_in = fopen(infile, "r");
847
0
    if (!fp_in)
848
0
    {
849
0
      mutt_perror("fopen");
850
0
      goto bail;
851
0
    }
852
853
0
    mutt_file_copy_stream(fp_in, fp_filter);
854
0
    mutt_file_fclose(&fp_in);
855
0
  }
856
857
0
  mutt_file_fclose(&fp_filter);
858
0
  rc = 1;
859
860
0
bail:
861
0
  if (outfile && *outfile)
862
0
  {
863
0
    close(out);
864
0
    if (rc == 0)
865
0
      unlink(outfile);
866
0
    else if (is_flowed)
867
0
      mutt_rfc3676_space_stuff_attachment(NULL, outfile);
868
0
  }
869
870
0
  mutt_file_fclose(&fp_unstuff);
871
0
  mutt_file_fclose(&fp_filter);
872
0
  mutt_file_fclose(&fp_in);
873
874
0
  if (unlink_unstuff)
875
0
    mutt_file_unlink(buf_string(unstuff_tempfile));
876
0
  buf_pool_release(&unstuff_tempfile);
877
878
  /* check for error exit from child process */
879
0
  if ((pid > 0) && (filter_wait(pid) != 0))
880
0
    rc = 0;
881
882
0
  const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
883
0
  if ((rc == 0) || c_wait_key)
884
0
    mutt_any_key_to_continue(NULL);
885
0
  return rc;
886
0
}
887
888
/**
889
 * save_attachment_open - Open a file to write an attachment to
890
 * @param path Path to file to open
891
 * @param opt  Save option, see #SaveAttach
892
 * @retval ptr File handle to attachment file
893
 */
894
static FILE *save_attachment_open(const char *path, enum SaveAttach opt)
895
0
{
896
0
  if (opt == MUTT_SAVE_APPEND)
897
0
    return fopen(path, "a");
898
0
  if (opt == MUTT_SAVE_OVERWRITE)
899
0
    return fopen(path, "w");
900
901
0
  return mutt_file_fopen(path, "w");
902
0
}
903
904
/**
905
 * mutt_save_attachment - Save an attachment
906
 * @param fp   Source file stream. Can be NULL
907
 * @param m    Email Body
908
 * @param path Where to save the attachment
909
 * @param opt  Save option, see #SaveAttach
910
 * @param e    Current Email. Can be NULL
911
 * @retval  0 Success
912
 * @retval -1 Error
913
 */
914
int mutt_save_attachment(FILE *fp, struct Body *m, const char *path,
915
                         enum SaveAttach opt, struct Email *e)
916
0
{
917
0
  if (!m)
918
0
    return -1;
919
920
0
  if (fp)
921
0
  {
922
    /* recv mode */
923
924
0
    if (e && m->email && (m->encoding != ENC_BASE64) &&
925
0
        (m->encoding != ENC_QUOTED_PRINTABLE) && mutt_is_message_type(m->type, m->subtype))
926
0
    {
927
      /* message type attachments are written to mail folders. */
928
929
0
      char buf[8192] = { 0 };
930
0
      struct Message *msg = NULL;
931
0
      CopyHeaderFlags chflags = CH_NO_FLAGS;
932
0
      int rc = -1;
933
934
0
      struct Email *e_new = m->email;
935
0
      e_new->msgno = e->msgno; /* required for MH/maildir */
936
0
      e_new->read = true;
937
938
0
      if (!mutt_file_seek(fp, m->offset, SEEK_SET))
939
0
        return -1;
940
0
      if (!fgets(buf, sizeof(buf), fp))
941
0
        return -1;
942
0
      struct Mailbox *m_att = mx_path_resolve(path);
943
0
      if (!mx_mbox_open(m_att, MUTT_APPEND | MUTT_QUIET))
944
0
      {
945
0
        mailbox_free(&m_att);
946
0
        return -1;
947
0
      }
948
0
      msg = mx_msg_open_new(m_att, e_new,
949
0
                            is_from(buf, NULL, 0, NULL) ? MUTT_MSG_NO_FLAGS : MUTT_ADD_FROM);
950
0
      if (!msg)
951
0
      {
952
0
        mx_mbox_close(m_att);
953
0
        return -1;
954
0
      }
955
0
      if ((m_att->type == MUTT_MBOX) || (m_att->type == MUTT_MMDF))
956
0
        chflags = CH_FROM | CH_UPDATE_LEN;
957
0
      chflags |= ((m_att->type == MUTT_MAILDIR) ? CH_NOSTATUS : CH_UPDATE);
958
0
      if ((mutt_copy_message_fp(msg->fp, fp, e_new, MUTT_CM_NO_FLAGS, chflags, 0) == 0) &&
959
0
          (mx_msg_commit(m_att, msg) == 0))
960
0
      {
961
0
        rc = 0;
962
0
      }
963
0
      else
964
0
      {
965
0
        rc = -1;
966
0
      }
967
968
0
      mx_msg_close(m_att, &msg);
969
0
      mx_mbox_close(m_att);
970
0
      return rc;
971
0
    }
972
0
    else
973
0
    {
974
      /* In recv mode, extract from folder and decode */
975
976
0
      struct State state = { 0 };
977
978
0
      state.fp_out = save_attachment_open(path, opt);
979
0
      if (!state.fp_out)
980
0
      {
981
0
        mutt_perror("fopen");
982
0
        return -1;
983
0
      }
984
0
      if (!mutt_file_seek((state.fp_in = fp), m->offset, SEEK_SET))
985
0
      {
986
0
        mutt_file_fclose(&state.fp_out);
987
0
        return -1;
988
0
      }
989
0
      mutt_decode_attachment(m, &state);
990
991
0
      if (mutt_file_fsync_close(&state.fp_out) != 0)
992
0
      {
993
0
        mutt_perror("fclose");
994
0
        return -1;
995
0
      }
996
0
    }
997
0
  }
998
0
  else
999
0
  {
1000
0
    if (!m->filename)
1001
0
      return -1;
1002
1003
    /* In send mode, just copy file */
1004
1005
0
    FILE *fp_old = fopen(m->filename, "r");
1006
0
    if (!fp_old)
1007
0
    {
1008
0
      mutt_perror("fopen");
1009
0
      return -1;
1010
0
    }
1011
1012
0
    FILE *fp_new = save_attachment_open(path, opt);
1013
0
    if (!fp_new)
1014
0
    {
1015
0
      mutt_perror("fopen");
1016
0
      mutt_file_fclose(&fp_old);
1017
0
      return -1;
1018
0
    }
1019
1020
0
    if (mutt_file_copy_stream(fp_old, fp_new) == -1)
1021
0
    {
1022
0
      mutt_error(_("Write fault"));
1023
0
      mutt_file_fclose(&fp_old);
1024
0
      mutt_file_fclose(&fp_new);
1025
0
      return -1;
1026
0
    }
1027
0
    mutt_file_fclose(&fp_old);
1028
0
    if (mutt_file_fsync_close(&fp_new) != 0)
1029
0
    {
1030
0
      mutt_error(_("Write fault"));
1031
0
      return -1;
1032
0
    }
1033
0
  }
1034
1035
0
  return 0;
1036
0
}
1037
1038
/**
1039
 * mutt_decode_save_attachment - Decode, then save an attachment
1040
 * @param fp         File to read from (OPTIONAL)
1041
 * @param m          Attachment
1042
 * @param path       Path to save the Attachment to
1043
 * @param flags      Flags, e.g. #STATE_DISPLAY
1044
 * @param opt        Save option, see #SaveAttach
1045
 * @retval 0  Success
1046
 * @retval -1 Error
1047
 */
1048
int mutt_decode_save_attachment(FILE *fp, struct Body *m, const char *path,
1049
                                StateFlags flags, enum SaveAttach opt)
1050
0
{
1051
0
  struct State state = { 0 };
1052
0
  unsigned int saved_encoding = 0;
1053
0
  struct Body *saved_parts = NULL;
1054
0
  struct Email *e_saved = NULL;
1055
0
  int rc = 0;
1056
1057
0
  state.flags = flags;
1058
1059
0
  if (opt == MUTT_SAVE_APPEND)
1060
0
    state.fp_out = fopen(path, "a");
1061
0
  else if (opt == MUTT_SAVE_OVERWRITE)
1062
0
    state.fp_out = fopen(path, "w");
1063
0
  else
1064
0
    state.fp_out = mutt_file_fopen(path, "w");
1065
1066
0
  if (!state.fp_out)
1067
0
  {
1068
0
    mutt_perror("fopen");
1069
0
    return -1;
1070
0
  }
1071
1072
0
  if (fp)
1073
0
  {
1074
0
    state.fp_in = fp;
1075
0
    state.flags |= STATE_CHARCONV;
1076
0
  }
1077
0
  else
1078
0
  {
1079
    /* When called from the compose menu, the attachment isn't parsed,
1080
     * so we need to do it here. */
1081
0
    state.fp_in = fopen(m->filename, "r");
1082
0
    if (!state.fp_in)
1083
0
    {
1084
0
      mutt_perror("fopen");
1085
0
      mutt_file_fclose(&state.fp_out);
1086
0
      return -1;
1087
0
    }
1088
1089
0
    struct stat st = { 0 };
1090
0
    if (fstat(fileno(state.fp_in), &st) == -1)
1091
0
    {
1092
0
      mutt_perror("stat");
1093
0
      mutt_file_fclose(&state.fp_in);
1094
0
      mutt_file_fclose(&state.fp_out);
1095
0
      return -1;
1096
0
    }
1097
1098
0
    saved_encoding = m->encoding;
1099
0
    if (!is_multipart(m))
1100
0
      m->encoding = ENC_8BIT;
1101
1102
0
    m->length = st.st_size;
1103
0
    m->offset = 0;
1104
0
    saved_parts = m->parts;
1105
0
    e_saved = m->email;
1106
0
    mutt_parse_part(state.fp_in, m);
1107
1108
0
    if (m->noconv || is_multipart(m))
1109
0
      state.flags |= STATE_CHARCONV;
1110
0
  }
1111
1112
0
  mutt_body_handler(m, &state);
1113
1114
0
  if (mutt_file_fsync_close(&state.fp_out) != 0)
1115
0
  {
1116
0
    mutt_perror("fclose");
1117
0
    rc = -1;
1118
0
  }
1119
0
  if (!fp)
1120
0
  {
1121
0
    m->length = 0;
1122
0
    m->encoding = saved_encoding;
1123
0
    if (saved_parts)
1124
0
    {
1125
0
      email_free(&m->email);
1126
0
      m->parts = saved_parts;
1127
0
      m->email = e_saved;
1128
0
    }
1129
0
    mutt_file_fclose(&state.fp_in);
1130
0
  }
1131
1132
0
  return rc;
1133
0
}
1134
1135
/**
1136
 * mutt_print_attachment - Print out an attachment
1137
 * @param fp File to write to
1138
 * @param a  Attachment
1139
 * @retval 1 Success
1140
 * @retval 0 Error
1141
 *
1142
 * Ok, the difference between send and receive:
1143
 * recv: Body->filename is a suggested name, and Mailbox|Email points
1144
 *       to the attachment in mailbox which is encoded
1145
 * send: Body->filename points to the un-encoded file which contains the
1146
 *       attachment
1147
 */
1148
int mutt_print_attachment(FILE *fp, struct Body *a)
1149
0
{
1150
0
  char type[256] = { 0 };
1151
0
  pid_t pid;
1152
0
  FILE *fp_in = NULL, *fp_out = NULL;
1153
0
  bool unlink_newfile = false;
1154
0
  struct Buffer *newfile = buf_pool_get();
1155
0
  struct Buffer *cmd = buf_pool_get();
1156
1157
0
  int rc = 0;
1158
1159
0
  snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
1160
1161
0
  if (mailcap_lookup(a, type, sizeof(type), NULL, MUTT_MC_PRINT))
1162
0
  {
1163
0
    mutt_debug(LL_DEBUG2, "Using mailcap\n");
1164
1165
0
    struct MailcapEntry *entry = mailcap_entry_new();
1166
0
    mailcap_lookup(a, type, sizeof(type), entry, MUTT_MC_PRINT);
1167
1168
0
    char *sanitized_fname = mutt_str_dup(a->filename);
1169
    /* In send mode (!fp), we allow slashes because those are part of
1170
     * the tempfile.  The path will be removed in expand_filename */
1171
0
    mutt_file_sanitize_filename(sanitized_fname, fp ? true : false);
1172
0
    mailcap_expand_filename(entry->nametemplate, sanitized_fname, newfile);
1173
0
    FREE(&sanitized_fname);
1174
1175
0
    if (mutt_save_attachment(fp, a, buf_string(newfile), MUTT_SAVE_NO_FLAGS, NULL) == -1)
1176
0
    {
1177
0
      goto mailcap_cleanup;
1178
0
    }
1179
0
    unlink_newfile = 1;
1180
1181
0
    mutt_rfc3676_space_unstuff_attachment(a, buf_string(newfile));
1182
1183
0
    buf_strcpy(cmd, entry->printcommand);
1184
1185
0
    bool piped = mailcap_expand_command(a, buf_string(newfile), type, cmd);
1186
1187
0
    mutt_endwin();
1188
1189
0
    const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
1190
    /* interactive program */
1191
0
    if (piped)
1192
0
    {
1193
0
      fp_in = fopen(buf_string(newfile), "r");
1194
0
      if (!fp_in)
1195
0
      {
1196
0
        mutt_perror("fopen");
1197
0
        mailcap_entry_free(&entry);
1198
0
        goto mailcap_cleanup;
1199
0
      }
1200
1201
0
      pid = filter_create(buf_string(cmd), &fp_out, NULL, NULL, EnvList);
1202
0
      if (pid < 0)
1203
0
      {
1204
0
        mutt_perror(_("Can't create filter"));
1205
0
        mailcap_entry_free(&entry);
1206
0
        mutt_file_fclose(&fp_in);
1207
0
        goto mailcap_cleanup;
1208
0
      }
1209
0
      mutt_file_copy_stream(fp_in, fp_out);
1210
0
      mutt_file_fclose(&fp_out);
1211
0
      mutt_file_fclose(&fp_in);
1212
0
      if (filter_wait(pid) || c_wait_key)
1213
0
        mutt_any_key_to_continue(NULL);
1214
0
    }
1215
0
    else
1216
0
    {
1217
0
      int rc2 = mutt_system(buf_string(cmd));
1218
0
      if (rc2 == -1)
1219
0
        mutt_debug(LL_DEBUG1, "Error running \"%s\"\n", cmd->data);
1220
1221
0
      if ((rc2 != 0) || c_wait_key)
1222
0
        mutt_any_key_to_continue(NULL);
1223
0
    }
1224
1225
0
    rc = 1;
1226
1227
0
  mailcap_cleanup:
1228
0
    if (unlink_newfile)
1229
0
      mutt_file_unlink(buf_string(newfile));
1230
1231
0
    mailcap_entry_free(&entry);
1232
0
    goto out;
1233
0
  }
1234
1235
0
  const char *const c_print_command = cs_subset_string(NeoMutt->sub, "print_command");
1236
0
  if (mutt_istr_equal("text/plain", type) || mutt_istr_equal("application/postscript", type))
1237
0
  {
1238
0
    rc = (mutt_pipe_attachment(fp, a, NONULL(c_print_command), NULL));
1239
0
    goto out;
1240
0
  }
1241
0
  else if (mutt_can_decode(a))
1242
0
  {
1243
    /* decode and print */
1244
1245
0
    fp_in = NULL;
1246
0
    fp_out = NULL;
1247
1248
0
    buf_mktemp(newfile);
1249
0
    if (mutt_decode_save_attachment(fp, a, buf_string(newfile), STATE_PRINTING,
1250
0
                                    MUTT_SAVE_NO_FLAGS) == 0)
1251
0
    {
1252
0
      unlink_newfile = true;
1253
0
      mutt_debug(LL_DEBUG2, "successfully decoded %s type attachment to %s\n",
1254
0
                 type, buf_string(newfile));
1255
1256
0
      fp_in = fopen(buf_string(newfile), "r");
1257
0
      if (!fp_in)
1258
0
      {
1259
0
        mutt_perror("fopen");
1260
0
        goto decode_cleanup;
1261
0
      }
1262
1263
0
      mutt_debug(LL_DEBUG2, "successfully opened %s read-only\n", buf_string(newfile));
1264
1265
0
      mutt_endwin();
1266
0
      pid = filter_create(NONULL(c_print_command), &fp_out, NULL, NULL, EnvList);
1267
0
      if (pid < 0)
1268
0
      {
1269
0
        mutt_perror(_("Can't create filter"));
1270
0
        goto decode_cleanup;
1271
0
      }
1272
1273
0
      mutt_debug(LL_DEBUG2, "Filter created\n");
1274
1275
0
      mutt_file_copy_stream(fp_in, fp_out);
1276
1277
0
      mutt_file_fclose(&fp_out);
1278
0
      mutt_file_fclose(&fp_in);
1279
1280
0
      const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
1281
0
      if ((filter_wait(pid) != 0) || c_wait_key)
1282
0
        mutt_any_key_to_continue(NULL);
1283
0
      rc = 1;
1284
0
    }
1285
0
  decode_cleanup:
1286
0
    mutt_file_fclose(&fp_in);
1287
0
    mutt_file_fclose(&fp_out);
1288
0
    if (unlink_newfile)
1289
0
      mutt_file_unlink(buf_string(newfile));
1290
0
  }
1291
0
  else
1292
0
  {
1293
0
    mutt_error(_("I don't know how to print that"));
1294
0
    rc = 0;
1295
0
  }
1296
1297
0
out:
1298
0
  buf_pool_release(&newfile);
1299
0
  buf_pool_release(&cmd);
1300
1301
0
  return rc;
1302
0
}
1303
1304
/**
1305
 * mutt_add_temp_attachment - Add file to list of temporary attachments
1306
 * @param filename filename with full path
1307
 */
1308
void mutt_add_temp_attachment(const char *filename)
1309
0
{
1310
0
  mutt_list_insert_tail(&TempAttachmentsList, mutt_str_dup(filename));
1311
0
}
1312
1313
/**
1314
 * mutt_temp_attachments_cleanup - Delete all temporary attachments
1315
 */
1316
void mutt_temp_attachments_cleanup(void)
1317
0
{
1318
0
  struct ListNode *np = NULL;
1319
1320
0
  STAILQ_FOREACH(np, &TempAttachmentsList, entries)
1321
0
  {
1322
0
    (void) mutt_file_chmod_add(np->data, S_IWUSR);
1323
0
    mutt_file_unlink(np->data);
1324
0
  }
1325
1326
0
  mutt_list_free(&TempAttachmentsList);
1327
0
}