Coverage Report

Created: 2023-06-07 06:15

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