• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

neomutt / neomutt / 22049757745

15 Feb 2026 05:32PM UTC coverage: 42.487% (+0.3%) from 42.162%
22049757745

push

github

flatcap
merge: modules: encapsulate global data 2

 * mod: send init/cleanup/data
 * cmd: move my-header to send
 * send: encapsulate globals
 * mod: alias init/cleanup/data
 * cmd: move alternates,alias to alias
 * alias: encapsulate globals
 * cmd: move tag-formats,-transforms to email
 * email: encapsulate globals

102 of 138 new or added lines in 9 files covered. (73.91%)

2339 existing lines in 12 files now uncovered.

12020 of 28291 relevant lines covered (42.49%)

2766.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

1.06
/attach/mutt_attach.c
1
/**
2
 * @file
3
 * Handling of email attachments
4
 *
5
 * @authors
6
 * Copyright (C) 2017-2021 Pietro Cerutti <gahr@gahr.ch>
7
 * Copyright (C) 2017-2026 Richard Russon <rich@flatcap.org>
8
 * Copyright (C) 2021 Ihor Antonov <ihor@antonovs.family>
9
 * Copyright (C) 2022 David Purton <dcpurton@marshwiggle.net>
10
 * Copyright (C) 2023 Dennis Schön <mail@dennis-schoen.de>
11
 *
12
 * @copyright
13
 * This program is free software: you can redistribute it and/or modify it under
14
 * the terms of the GNU General Public License as published by the Free Software
15
 * Foundation, either version 2 of the License, or (at your option) any later
16
 * version.
17
 *
18
 * This program is distributed in the hope that it will be useful, but WITHOUT
19
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
21
 * details.
22
 *
23
 * You should have received a copy of the GNU General Public License along with
24
 * this program.  If not, see <http://www.gnu.org/licenses/>.
25
 */
26

27
/**
28
 * @page attach_mutt_attach Shared attachments functions
29
 *
30
 * Handling of email attachments
31
 */
32

33
#include "config.h"
34
#include <errno.h>
35
#include <fcntl.h>
36
#include <stdbool.h>
37
#include <stdio.h>
38
#include <string.h>
39
#include <sys/stat.h>
40
#include <sys/types.h>
41
#include <sys/wait.h>
42
#include <unistd.h>
43
#include "mutt/lib.h"
44
#include "config/lib.h"
45
#include "email/lib.h"
46
#include "core/lib.h"
47
#include "gui/lib.h"
48
#include "mutt.h"
49
#include "mutt_attach.h"
50
#include "lib.h"
51
#include "imap/lib.h"
52
#include "ncrypt/lib.h"
53
#include "pager/lib.h"
54
#include "question/lib.h"
55
#include "send/lib.h"
56
#include "attach.h"
57
#include "cid.h"
58
#include "module_data.h"
59
#include "muttlib.h"
60
#include "mx.h"
61

62
/**
63
 * mutt_get_tmp_attachment - Get a temporary copy of an attachment
64
 * @param b Attachment to copy
65
 * @retval  0 Success
66
 * @retval -1 Error
67
 */
68
int mutt_get_tmp_attachment(struct Body *b)
×
69
{
70
  char type[256] = { 0 };
×
71

72
  if (b->unlink)
×
73
    return 0;
74

75
  struct Buffer *tempfile = buf_pool_get();
×
76
  struct MailcapEntry *entry = mailcap_entry_new();
×
77
  snprintf(type, sizeof(type), "%s/%s", BODY_TYPE(b), b->subtype);
×
78
  mailcap_lookup(b, type, sizeof(type), entry, MUTT_MC_NO_FLAGS);
×
79
  mailcap_expand_filename(entry->nametemplate, b->filename, tempfile);
×
80

81
  mailcap_entry_free(&entry);
×
82

83
  FILE *fp_in = NULL, *fp_out = NULL;
×
84
  if ((fp_in = mutt_file_fopen(b->filename, "r")) &&
×
85
      (fp_out = mutt_file_fopen(buf_string(tempfile), "w")))
×
86
  {
×
87
    mutt_file_copy_stream(fp_in, fp_out);
×
88
    mutt_str_replace(&b->filename, buf_string(tempfile));
×
89
    b->unlink = true;
×
90

91
    struct stat st = { 0 };
×
92
    if ((fstat(fileno(fp_in), &st) == 0) && (b->stamp >= st.st_mtime))
×
93
    {
94
      mutt_stamp_attachment(b);
×
95
    }
96
  }
97
  else
98
  {
99
    mutt_perror("%s", fp_in ? buf_string(tempfile) : b->filename);
×
100
  }
101

102
  mutt_file_fclose(&fp_in);
×
103
  mutt_file_fclose(&fp_out);
×
104

105
  buf_pool_release(&tempfile);
×
106

107
  return b->unlink ? 0 : -1;
×
108
}
109

110
/**
111
 * mutt_compose_attachment - Create an attachment
112
 * @param b Body of email
113
 * @retval 1 Require full screen redraw
114
 * @retval 0 Otherwise
115
 */
116
int mutt_compose_attachment(struct Body *b)
×
117
{
118
  char type[256] = { 0 };
×
119
  struct MailcapEntry *entry = mailcap_entry_new();
×
120
  bool unlink_newfile = false;
121
  int rc = 0;
122
  struct Buffer *cmd = buf_pool_get();
×
123
  struct Buffer *newfile = buf_pool_get();
×
124
  struct Buffer *tempfile = buf_pool_get();
×
125

126
  snprintf(type, sizeof(type), "%s/%s", BODY_TYPE(b), b->subtype);
×
127
  if (mailcap_lookup(b, type, sizeof(type), entry, MUTT_MC_COMPOSE))
×
128
  {
129
    if (entry->composecommand || entry->composetypecommand)
×
130
    {
131
      if (entry->composetypecommand)
×
132
        buf_strcpy(cmd, entry->composetypecommand);
×
133
      else
134
        buf_strcpy(cmd, entry->composecommand);
×
135

136
      mailcap_expand_filename(entry->nametemplate, b->filename, newfile);
×
137
      mutt_debug(LL_DEBUG1, "oldfile: %s     newfile: %s\n", b->filename,
×
138
                 buf_string(newfile));
139
      if (mutt_file_symlink(b->filename, buf_string(newfile)) == -1)
×
140
      {
141
        if (query_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES)
×
142
          goto bailout;
×
143
        buf_strcpy(newfile, b->filename);
×
144
      }
145
      else
146
      {
147
        unlink_newfile = true;
148
      }
149

150
      if (mailcap_expand_command(b, buf_string(newfile), type, cmd))
×
151
      {
152
        /* For now, editing requires a file, no piping */
153
        mutt_error(_("Mailcap compose entry requires %%s"));
×
154
      }
155
      else
156
      {
157
        int r;
158

159
        mutt_endwin();
×
160
        r = mutt_system(buf_string(cmd));
×
161
        if (r == -1)
×
162
          mutt_error(_("Error running \"%s\""), buf_string(cmd));
×
163

164
        if ((r != -1) && entry->composetypecommand)
×
165
        {
166
          FILE *fp = mutt_file_fopen(b->filename, "r");
×
167
          if (!fp)
×
168
          {
169
            mutt_perror(_("Failure to open file to parse headers"));
×
170
            goto bailout;
×
171
          }
172

173
          struct Body *b_mime = mutt_read_mime_header(fp, 0);
×
174
          if (b_mime)
×
175
          {
176
            if (!TAILQ_EMPTY(&b_mime->parameter))
×
177
            {
178
              mutt_param_free(&b->parameter);
×
179
              b->parameter = b_mime->parameter;
×
180
              TAILQ_INIT(&b_mime->parameter);
×
181
            }
182
            if (b_mime->description)
×
183
            {
184
              FREE(&b->description);
×
185
              b->description = b_mime->description;
×
186
              b_mime->description = NULL;
×
187
            }
188
            if (b_mime->form_name)
×
189
            {
190
              FREE(&b->form_name);
×
191
              b->form_name = b_mime->form_name;
×
192
              b_mime->form_name = NULL;
×
193
            }
194

195
            /* Remove headers by copying out data to another file, then
196
             * copying the file back */
197
            const LOFF_T offset = b_mime->offset;
×
198
            mutt_body_free(&b_mime);
×
199
            if (!mutt_file_seek(fp, offset, SEEK_SET))
×
200
            {
201
              goto bailout;
×
202
            }
203

204
            buf_mktemp(tempfile);
×
205
            FILE *fp_tmp = mutt_file_fopen(buf_string(tempfile), "w");
×
206
            if (!fp_tmp)
×
207
            {
208
              mutt_perror(_("Failure to open file to strip headers"));
×
209
              mutt_file_fclose(&fp);
×
210
              goto bailout;
×
211
            }
212
            mutt_file_copy_stream(fp, fp_tmp);
×
213
            mutt_file_fclose(&fp);
×
214
            mutt_file_fclose(&fp_tmp);
×
215
            mutt_file_unlink(b->filename);
×
216
            if (mutt_file_rename(buf_string(tempfile), b->filename) != 0)
×
217
            {
218
              mutt_perror(_("Failure to rename file"));
×
219
              goto bailout;
×
220
            }
221
          }
222
        }
223
      }
224
    }
225
  }
226
  else
227
  {
228
    mutt_message(_("No mailcap compose entry for %s, creating empty file"), type);
×
229
    rc = 1;
230
    goto bailout;
×
231
  }
232

233
  rc = 1;
234

235
bailout:
×
236

237
  if (unlink_newfile)
×
238
    unlink(buf_string(newfile));
×
239

240
  buf_pool_release(&cmd);
×
241
  buf_pool_release(&newfile);
×
242
  buf_pool_release(&tempfile);
×
243

244
  mailcap_entry_free(&entry);
×
245
  return rc;
×
246
}
247

248
/**
249
 * mutt_edit_attachment - Edit an attachment
250
 * @param b Email containing attachment
251
 * @retval true  Editor found
252
 * @retval false Editor not found
253
 *
254
 * Currently, this only works for send mode, as it assumes that the
255
 * Body->filename actually contains the information.  I'm not sure
256
 * we want to deal with editing attachments we've already received,
257
 * so this should be ok.
258
 *
259
 * Returning 0 is useful to tell the calling menu to redraw
260
 */
261
bool mutt_edit_attachment(struct Body *b)
×
262
{
263
  char type[256] = { 0 };
×
264
  struct MailcapEntry *entry = mailcap_entry_new();
×
265
  bool unlink_newfile = false;
266
  bool rc = false;
267
  struct Buffer *cmd = buf_pool_get();
×
268
  struct Buffer *newfile = buf_pool_get();
×
269

270
  snprintf(type, sizeof(type), "%s/%s", BODY_TYPE(b), b->subtype);
×
271
  if (mailcap_lookup(b, type, sizeof(type), entry, MUTT_MC_EDIT))
×
272
  {
273
    if (entry->editcommand)
×
274
    {
275
      buf_strcpy(cmd, entry->editcommand);
×
276
      mailcap_expand_filename(entry->nametemplate, b->filename, newfile);
×
277
      mutt_debug(LL_DEBUG1, "oldfile: %s     newfile: %s\n", b->filename,
×
278
                 buf_string(newfile));
279
      if (mutt_file_symlink(b->filename, buf_string(newfile)) == -1)
×
280
      {
281
        if (query_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES)
×
282
          goto bailout;
×
283
        buf_strcpy(newfile, b->filename);
×
284
      }
285
      else
286
      {
287
        unlink_newfile = true;
288
      }
289

290
      if (mailcap_expand_command(b, buf_string(newfile), type, cmd))
×
291
      {
292
        /* For now, editing requires a file, no piping */
293
        mutt_error(_("Mailcap Edit entry requires %%s"));
×
294
        goto bailout;
×
295
      }
296
      else
297
      {
298
        mutt_endwin();
×
299
        if (mutt_system(buf_string(cmd)) == -1)
×
300
        {
301
          mutt_error(_("Error running \"%s\""), buf_string(cmd));
×
302
          goto bailout;
×
303
        }
304
      }
305
    }
306
  }
307
  else if (b->type == TYPE_TEXT)
×
308
  {
309
    /* On text, default to editor */
310
    const char *const c_editor = cs_subset_string(NeoMutt->sub, "editor");
×
311
    mutt_edit_file(NONULL(c_editor), b->filename);
×
312
  }
313
  else
314
  {
315
    mutt_error(_("No mailcap edit entry for %s"), type);
×
316
    goto bailout;
×
317
  }
318

319
  rc = true;
320

321
bailout:
×
322

323
  if (unlink_newfile)
×
324
    unlink(buf_string(newfile));
×
325

326
  buf_pool_release(&cmd);
×
327
  buf_pool_release(&newfile);
×
328

329
  mailcap_entry_free(&entry);
×
330
  return rc;
×
331
}
332

333
/**
334
 * mutt_check_lookup_list - Update the mime type
335
 * @param b    Message attachment body
336
 * @param type Buffer with mime type of attachment in "type/subtype" format
337
 * @param len  Buffer length
338
 */
339
void mutt_check_lookup_list(struct Body *b, char *type, size_t len)
×
340
{
UNCOV
341
  struct AttachModuleData *md = neomutt_get_module_data(NeoMutt, MODULE_ID_ATTACH);
×
342
  ASSERT(md);
×
343

344
  struct ListNode *np = NULL;
345
  STAILQ_FOREACH(np, &md->mime_lookup, entries)
×
346
  {
347
    const int i = (int) mutt_str_len(np->data) - 1;
×
UNCOV
348
    if (((i > 0) && (np->data[i - 1] == '/') && (np->data[i] == '*') &&
×
349
         mutt_istrn_equal(type, np->data, i)) ||
×
UNCOV
350
        mutt_istr_equal(type, np->data))
×
351
    {
352
      struct Body tmp = { 0 };
×
353
      enum ContentType n;
354
      if ((n = mutt_lookup_mime_type(&tmp, b->filename)) != TYPE_OTHER ||
×
UNCOV
355
          (n = mutt_lookup_mime_type(&tmp, b->description)) != TYPE_OTHER)
×
356
      {
UNCOV
357
        snprintf(type, len, "%s/%s",
×
358
                 (n == TYPE_AUDIO)       ? "audio" :
359
                 (n == TYPE_APPLICATION) ? "application" :
360
                 (n == TYPE_IMAGE)       ? "image" :
361
                 (n == TYPE_MESSAGE)     ? "message" :
362
                 (n == TYPE_MODEL)       ? "model" :
363
                 (n == TYPE_MULTIPART)   ? "multipart" :
364
                 (n == TYPE_TEXT)        ? "text" :
365
                 (n == TYPE_VIDEO)       ? "video" :
366
                                           "other",
367
                 tmp.subtype);
368
        mutt_debug(LL_DEBUG1, "\"%s\" -> %s\n", b->filename, type);
×
369
      }
UNCOV
370
      FREE(&tmp.subtype);
×
371
      FREE(&tmp.xtype);
×
372
    }
373
  }
UNCOV
374
}
×
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
 */
UNCOV
389
static int wait_interactive_filter(pid_t pid)
×
390
{
391
  int rc;
392

UNCOV
393
  rc = imap_wait_keep_alive(pid);
×
394
  mutt_sig_unblock_system(true);
×
UNCOV
395
  rc = WIFEXITED(rc) ? WEXITSTATUS(rc) : -1;
×
396

UNCOV
397
  return rc;
×
398
}
399

400
/**
401
 * mutt_view_attachment - View an attachment
402
 * @param fp     Source file stream. Can be NULL
403
 * @param b      The message body containing the attachment
404
 * @param mode   How the attachment should be viewed, see #ViewAttachMode
405
 * @param e      Current Email. Can be NULL
406
 * @param actx   Attachment context
407
 * @param win    Window
408
 * @retval 0   The viewer is run and exited successfully
409
 * @retval -1  Error
410
 * @retval num Return value of mutt_do_pager() when it is used
411
 *
412
 * Display a message attachment using the viewer program configured in mailcap.
413
 * If there is no mailcap entry for a file type, view the image as text.
414
 * Viewer processes are opened and waited on synchronously so viewing an
415
 * attachment this way will block the main neomutt process until the viewer process
416
 * exits.
417
 */
UNCOV
418
int mutt_view_attachment(FILE *fp, struct Body *b, enum ViewAttachMode mode,
×
419
                         struct Email *e, struct AttachCtx *actx, struct MuttWindow *win)
420
{
421
  bool use_mailcap = false;
422
  bool use_pipe = false;
423
  bool use_pager = true;
424
  char type[256] = { 0 };
×
UNCOV
425
  char desc[512] = { 0 };
×
UNCOV
426
  char *fname = NULL;
×
UNCOV
427
  struct MailcapEntry *entry = NULL;
×
428
  int rc = -1;
429
  bool has_tempfile = false;
430
  bool unlink_pagerfile = false;
431

UNCOV
432
  bool is_message = mutt_is_message_type(b->type, b->subtype);
×
UNCOV
433
  if ((WithCrypto != 0) && is_message && b->email &&
×
UNCOV
434
      (b->email->security & SEC_ENCRYPT) && !crypt_valid_passphrase(b->email->security))
×
435
  {
436
    return rc;
437
  }
438

UNCOV
439
  struct Buffer *tempfile = buf_pool_get();
×
440
  struct Buffer *pagerfile = buf_pool_get();
×
441
  struct Buffer *cmd = buf_pool_get();
×
442

443
  use_mailcap = ((mode == MUTT_VA_MAILCAP) ||
×
UNCOV
444
                 ((mode == MUTT_VA_REGULAR) && mutt_needs_mailcap(b)) ||
×
445
                 (mode == MUTT_VA_PAGER));
446
  snprintf(type, sizeof(type), "%s/%s", BODY_TYPE(b), b->subtype);
×
447

UNCOV
448
  char columns[16] = { 0 };
×
449
  snprintf(columns, sizeof(columns), "%d", win->state.cols);
×
UNCOV
450
  envlist_set(&NeoMutt->env, "COLUMNS", columns, true);
×
451

452
  if (use_mailcap)
×
453
  {
UNCOV
454
    entry = mailcap_entry_new();
×
455
    enum MailcapLookup mailcap_opt = (mode == MUTT_VA_PAGER) ? MUTT_MC_AUTOVIEW : MUTT_MC_NO_FLAGS;
×
UNCOV
456
    if (!mailcap_lookup(b, type, sizeof(type), entry, mailcap_opt))
×
457
    {
458
      if ((mode == MUTT_VA_REGULAR) || (mode == MUTT_VA_PAGER))
×
459
      {
460
        /* fallback to view as text */
UNCOV
461
        mailcap_entry_free(&entry);
×
UNCOV
462
        mutt_error(_("No matching mailcap entry found.  Viewing as text."));
×
463
        mode = MUTT_VA_AS_TEXT;
464
        use_mailcap = false;
465
      }
466
      else
467
      {
UNCOV
468
        goto return_error;
×
469
      }
470
    }
471
  }
472

473
  if (use_mailcap)
474
  {
475
    if (!entry->command)
×
476
    {
477
      mutt_error(_("MIME type not defined.  Can't view attachment."));
×
UNCOV
478
      goto return_error;
×
479
    }
UNCOV
480
    buf_strcpy(cmd, entry->command);
×
481

482
    fname = mutt_str_dup(b->filename);
×
483
    /* In send mode(!fp), we allow slashes because those are part of
484
     * the tempfile.  The path will be removed in expand_filename */
UNCOV
485
    mutt_file_sanitize_filename(fname, fp ? true : false);
×
486
    mailcap_expand_filename(entry->nametemplate, fname, tempfile);
×
487
    FREE(&fname);
×
488

UNCOV
489
    if (mutt_save_attachment(fp, b, buf_string(tempfile), 0, NULL) == -1)
×
490
      goto return_error;
×
491
    has_tempfile = true;
492

493
    mutt_rfc3676_space_unstuff_attachment(b, buf_string(tempfile));
×
494

495
    /* check for multipart/related and save attachments with b Content-ID */
496
    if (mutt_str_equal(type, "text/html"))
×
497
    {
498
      struct Body *related_ancestor = NULL;
499
      if (actx->body_idx && (WithCrypto != 0) && (e->security & SEC_ENCRYPT))
×
500
        related_ancestor = attach_body_ancestor(actx->body_idx[0], b, "related");
×
501
      else
502
        related_ancestor = attach_body_ancestor(e->body, b, "related");
×
503
      if (related_ancestor)
×
504
      {
505
        struct CidMapList cid_map_list = STAILQ_HEAD_INITIALIZER(cid_map_list);
×
UNCOV
506
        mutt_debug(LL_DEBUG2, "viewing text/html attachment in multipart/related group\n");
×
507
        /* save attachments and build cid_map_list Content-ID to filename mapping list */
UNCOV
508
        cid_save_attachments(related_ancestor->parts, &cid_map_list);
×
509
        /* replace Content-IDs with filenames */
UNCOV
510
        cid_to_filename(tempfile, &cid_map_list);
×
511
        /* empty Content-ID to filename mapping list */
UNCOV
512
        cid_map_list_clear(&cid_map_list);
×
513
      }
514
    }
515

UNCOV
516
    use_pipe = mailcap_expand_command(b, buf_string(tempfile), type, cmd);
×
517
    use_pager = entry->copiousoutput;
×
518
  }
519

UNCOV
520
  if (use_pager)
×
521
  {
522
    if (fp && !use_mailcap && b->filename)
×
523
    {
524
      /* recv case */
UNCOV
525
      buf_strcpy(pagerfile, b->filename);
×
UNCOV
526
      mutt_adv_mktemp(pagerfile);
×
527
    }
528
    else
529
    {
UNCOV
530
      buf_mktemp(pagerfile);
×
531
    }
532
  }
533

UNCOV
534
  if (use_mailcap)
×
535
  {
536
    pid_t pid = 0;
537
    int fd_temp = -1, fd_pager = -1;
538

539
    if (!use_pager)
×
540
      mutt_endwin();
×
541

542
    const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
×
543
    if (use_pager || use_pipe)
×
544
    {
UNCOV
545
      if (use_pager &&
×
546
          ((fd_pager = mutt_file_open(buf_string(pagerfile),
×
547
                                      O_CREAT | O_EXCL | O_WRONLY, 0600)) == -1))
548
      {
UNCOV
549
        mutt_perror("open");
×
UNCOV
550
        goto return_error;
×
551
      }
552
      unlink_pagerfile = true;
553

554
      if (use_pipe && ((fd_temp = open(buf_string(tempfile), 0)) == -1))
×
555
      {
556
        if (fd_pager != -1)
×
UNCOV
557
          close(fd_pager);
×
UNCOV
558
        mutt_perror("open");
×
UNCOV
559
        goto return_error;
×
560
      }
561
      unlink_pagerfile = true;
562

563
      pid = filter_create_fd(buf_string(cmd), NULL, NULL, NULL, use_pipe ? fd_temp : -1,
×
UNCOV
564
                             use_pager ? fd_pager : -1, -1, NeoMutt->env);
×
565

566
      if (pid == -1)
×
567
      {
568
        if (fd_pager != -1)
×
569
          close(fd_pager);
×
570

571
        if (fd_temp != -1)
×
572
          close(fd_temp);
×
573

UNCOV
574
        mutt_error(_("Can't create filter"));
×
575
        goto return_error;
×
576
      }
577

UNCOV
578
      if (use_pager)
×
579
      {
UNCOV
580
        if (b->description)
×
581
        {
UNCOV
582
          snprintf(desc, sizeof(desc), _("---Command: %-20.20s Description: %s"),
×
583
                   buf_string(cmd), b->description);
584
        }
585
        else
586
        {
587
          snprintf(desc, sizeof(desc), _("---Command: %-30.30s Attachment: %s"),
×
588
                   buf_string(cmd), type);
589
        }
UNCOV
590
        filter_wait(pid);
×
591
      }
592
      else
593
      {
UNCOV
594
        if (wait_interactive_filter(pid) || (entry->needsterminal && c_wait_key))
×
595
          mutt_any_key_to_continue(NULL);
×
596
      }
597

598
      if (fd_temp != -1)
×
UNCOV
599
        close(fd_temp);
×
UNCOV
600
      if (fd_pager != -1)
×
UNCOV
601
        close(fd_pager);
×
602
    }
603
    else
604
    {
605
      /* interactive cmd */
UNCOV
606
      int rv = mutt_system(buf_string(cmd));
×
607
      if (rv == -1)
×
608
        mutt_debug(LL_DEBUG1, "Error running \"%s\"\n", cmd->data);
×
609

UNCOV
610
      if ((rv != 0) || (entry->needsterminal && c_wait_key))
×
UNCOV
611
        mutt_any_key_to_continue(NULL);
×
612
    }
613
  }
614
  else
615
  {
616
    /* Don't use mailcap; the attachment is viewed in the pager */
617

618
    if (mode == MUTT_VA_AS_TEXT)
×
619
    {
620
      /* just let me see the raw data */
UNCOV
621
      if (fp)
×
622
      {
623
        /* Viewing from a received message.
624
         *
625
         * Don't use mutt_save_attachment() because we want to perform charset
626
         * conversion since this will be displayed by the internal pager.  */
627
        struct State state = { 0 };
×
628

629
        state.fp_out = mutt_file_fopen(buf_string(pagerfile), "w");
×
UNCOV
630
        if (!state.fp_out)
×
631
        {
632
          mutt_debug(LL_DEBUG1, "mutt_file_fopen(%s) errno=%d %s\n",
×
633
                     buf_string(pagerfile), errno, strerror(errno));
634
          mutt_perror("%s", buf_string(pagerfile));
×
635
          goto return_error;
×
636
        }
637
        state.fp_in = fp;
×
UNCOV
638
        state.flags = STATE_CHARCONV;
×
UNCOV
639
        mutt_decode_attachment(b, &state);
×
UNCOV
640
        mutt_file_fclose(&state.fp_out);
×
641
      }
642
      else
643
      {
644
        /* in compose mode, just copy the file.  we can't use
645
         * mutt_decode_attachment() since it assumes the content-encoding has
646
         * already been applied */
UNCOV
647
        if (mutt_save_attachment(fp, b, buf_string(pagerfile), MUTT_SAVE_NO_FLAGS, NULL))
×
648
          goto return_error;
×
649
        unlink_pagerfile = true;
650
      }
UNCOV
651
      mutt_rfc3676_space_unstuff_attachment(b, buf_string(pagerfile));
×
652
    }
653
    else
654
    {
655
      StateFlags flags = STATE_DISPLAY | STATE_DISPLAY_ATTACH;
UNCOV
656
      const char *const c_pager = pager_get_pager(NeoMutt->sub);
×
UNCOV
657
      if (!c_pager)
×
658
        flags |= STATE_PAGER;
659

660
      /* Use built-in handler */
UNCOV
661
      if (mutt_decode_save_attachment(fp, b, buf_string(pagerfile), flags, MUTT_SAVE_NO_FLAGS))
×
662
      {
UNCOV
663
        goto return_error;
×
664
      }
665
      unlink_pagerfile = true;
666
    }
667

668
    if (b->description)
×
UNCOV
669
      mutt_str_copy(desc, b->description, sizeof(desc));
×
670
    else if (b->filename)
×
UNCOV
671
      snprintf(desc, sizeof(desc), _("---Attachment: %s: %s"), b->filename, type);
×
672
    else
UNCOV
673
      snprintf(desc, sizeof(desc), _("---Attachment: %s"), type);
×
674
  }
675

676
  /* We only reach this point if there have been no errors */
677

678
  if (use_pager)
×
679
  {
680
    struct PagerData pdata = { 0 };
681
    struct PagerView pview = { &pdata };
×
682

683
    pdata.actx = actx;
×
UNCOV
684
    pdata.body = b;
×
685
    pdata.fname = buf_string(pagerfile);
×
686
    pdata.fp = fp;
×
687

688
    pview.banner = desc;
×
UNCOV
689
    pview.flags = MUTT_PAGER_ATTACHMENT |
×
690
                  (is_message ? MUTT_PAGER_MESSAGE : MUTT_PAGER_NO_FLAGS) |
×
UNCOV
691
                  ((use_mailcap && entry->xneomuttnowrap) ? MUTT_PAGER_NOWRAP :
×
692
                                                            MUTT_PAGER_NO_FLAGS);
UNCOV
693
    pview.mode = PAGER_MODE_ATTACH;
×
694

UNCOV
695
    rc = mutt_do_pager(&pview, e);
×
696

UNCOV
697
    buf_reset(pagerfile);
×
698
    unlink_pagerfile = false;
699
  }
700
  else
701
  {
702
    rc = 0;
703
  }
704

UNCOV
705
return_error:
×
706

UNCOV
707
  if (!entry || !entry->xneomuttkeep)
×
708
  {
709
    if ((fp && !buf_is_empty(tempfile)) || has_tempfile)
×
710
    {
711
      /* add temporary file to list of files to be deleted on timeout hook */
UNCOV
712
      mutt_add_temp_attachment(buf_string(tempfile));
×
713
    }
714
  }
715

716
  mailcap_entry_free(&entry);
×
717

718
  if (unlink_pagerfile)
×
719
    mutt_file_unlink(buf_string(pagerfile));
×
720

721
  buf_pool_release(&tempfile);
×
UNCOV
722
  buf_pool_release(&pagerfile);
×
723
  buf_pool_release(&cmd);
×
UNCOV
724
  envlist_unset(&NeoMutt->env, "COLUMNS");
×
725

UNCOV
726
  return rc;
×
727
}
728

729
/**
730
 * mutt_pipe_attachment - Pipe an attachment to a command
731
 * @param fp      File to pipe into the command
732
 * @param b       Attachment
733
 * @param path    Path to command
734
 * @param outfile File to save output to
735
 * @retval 1 Success
736
 * @retval 0 Error
737
 */
UNCOV
738
int mutt_pipe_attachment(FILE *fp, struct Body *b, const char *path, const char *outfile)
×
739
{
740
  pid_t pid = 0;
741
  int out = -1, rc = 0;
742
  bool is_flowed = false;
743
  bool unlink_unstuff = false;
744
  FILE *fp_filter = NULL, *fp_unstuff = NULL, *fp_in = NULL;
×
UNCOV
745
  struct Buffer *unstuff_tempfile = NULL;
×
746

747
  if (outfile && *outfile)
×
748
  {
749
    out = mutt_file_open(outfile, O_CREAT | O_EXCL | O_WRONLY, 0600);
×
750
    if (out < 0)
×
751
    {
UNCOV
752
      mutt_perror("open");
×
UNCOV
753
      return 0;
×
754
    }
755
  }
756

757
  if (mutt_rfc3676_is_format_flowed(b))
×
758
  {
759
    is_flowed = true;
UNCOV
760
    unstuff_tempfile = buf_pool_get();
×
761
    buf_mktemp(unstuff_tempfile);
×
762
  }
763

764
  mutt_endwin();
×
765

766
  if (outfile && *outfile)
×
767
    pid = filter_create_fd(path, &fp_filter, NULL, NULL, -1, out, -1, NeoMutt->env);
×
768
  else
769
    pid = filter_create(path, &fp_filter, NULL, NULL, NeoMutt->env);
×
770
  if (pid < 0)
×
771
  {
UNCOV
772
    mutt_perror(_("Can't create filter"));
×
UNCOV
773
    goto bail;
×
774
  }
775

776
  /* recv case */
UNCOV
777
  if (fp)
×
778
  {
779
    struct State state = { 0 };
×
780

781
    /* perform charset conversion on text attachments when piping */
UNCOV
782
    state.flags = STATE_CHARCONV;
×
783

784
    if (is_flowed)
×
785
    {
786
      fp_unstuff = mutt_file_fopen(buf_string(unstuff_tempfile), "w");
×
787
      if (!fp_unstuff)
×
788
      {
UNCOV
789
        mutt_perror("mutt_file_fopen");
×
UNCOV
790
        goto bail;
×
791
      }
792
      unlink_unstuff = true;
793

794
      state.fp_in = fp;
×
UNCOV
795
      state.fp_out = fp_unstuff;
×
796
      mutt_decode_attachment(b, &state);
×
UNCOV
797
      mutt_file_fclose(&fp_unstuff);
×
798

799
      mutt_rfc3676_space_unstuff_attachment(b, buf_string(unstuff_tempfile));
×
800

801
      fp_unstuff = mutt_file_fopen(buf_string(unstuff_tempfile), "r");
×
802
      if (!fp_unstuff)
×
803
      {
804
        mutt_perror("mutt_file_fopen");
×
805
        goto bail;
×
806
      }
UNCOV
807
      mutt_file_copy_stream(fp_unstuff, fp_filter);
×
UNCOV
808
      mutt_file_fclose(&fp_unstuff);
×
809
    }
810
    else
811
    {
UNCOV
812
      state.fp_in = fp;
×
UNCOV
813
      state.fp_out = fp_filter;
×
UNCOV
814
      mutt_decode_attachment(b, &state);
×
815
    }
816
  }
817
  else
818
  {
819
    /* send case */
820
    const char *infile = NULL;
821

UNCOV
822
    if (is_flowed)
×
823
    {
824
      if (mutt_save_attachment(fp, b, buf_string(unstuff_tempfile),
×
825
                               MUTT_SAVE_NO_FLAGS, NULL) == -1)
826
      {
827
        goto bail;
×
828
      }
829
      unlink_unstuff = true;
UNCOV
830
      mutt_rfc3676_space_unstuff_attachment(b, buf_string(unstuff_tempfile));
×
UNCOV
831
      infile = buf_string(unstuff_tempfile);
×
832
    }
833
    else
834
    {
835
      infile = b->filename;
×
836
    }
837

838
    fp_in = mutt_file_fopen(infile, "r");
×
839
    if (!fp_in)
×
840
    {
UNCOV
841
      mutt_perror("fopen");
×
842
      goto bail;
×
843
    }
844

UNCOV
845
    mutt_file_copy_stream(fp_in, fp_filter);
×
846
    mutt_file_fclose(&fp_in);
×
847
  }
848

849
  mutt_file_fclose(&fp_filter);
×
850
  rc = 1;
851

852
bail:
×
853
  if (outfile && *outfile)
×
854
  {
855
    close(out);
×
856
    if (rc == 0)
×
UNCOV
857
      unlink(outfile);
×
UNCOV
858
    else if (is_flowed)
×
859
      mutt_rfc3676_space_stuff_attachment(NULL, outfile);
×
860
  }
861

UNCOV
862
  mutt_file_fclose(&fp_unstuff);
×
863
  mutt_file_fclose(&fp_filter);
×
864
  mutt_file_fclose(&fp_in);
×
865

UNCOV
866
  if (unlink_unstuff)
×
UNCOV
867
    mutt_file_unlink(buf_string(unstuff_tempfile));
×
868
  buf_pool_release(&unstuff_tempfile);
×
869

870
  /* check for error exit from child process */
871
  if ((pid > 0) && (filter_wait(pid) != 0))
×
872
    rc = 0;
873

UNCOV
874
  const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
×
UNCOV
875
  if ((rc == 0) || c_wait_key)
×
UNCOV
876
    mutt_any_key_to_continue(NULL);
×
877
  return rc;
878
}
879

880
/**
881
 * save_attachment_open - Open a file to write an attachment to
882
 * @param path Path to file to open
883
 * @param opt  Save option, see #SaveAttach
884
 * @retval ptr File handle to attachment file
885
 */
886
static FILE *save_attachment_open(const char *path, enum SaveAttach opt)
×
887
{
888
  if (opt == MUTT_SAVE_APPEND)
×
UNCOV
889
    return mutt_file_fopen_masked(path, "a");
×
890
  else
UNCOV
891
    return mutt_file_fopen_masked(path, "w");
×
892
}
893

894
/**
895
 * mutt_save_attachment - Save an attachment
896
 * @param fp   Source file stream. Can be NULL
897
 * @param b    Email Body
898
 * @param path Where to save the attachment
899
 * @param opt  Save option, see #SaveAttach
900
 * @param e    Current Email. Can be NULL
901
 * @retval  0 Success
902
 * @retval -1 Error
903
 */
904
int mutt_save_attachment(FILE *fp, struct Body *b, const char *path,
×
905
                         enum SaveAttach opt, struct Email *e)
906
{
907
  if (!b)
×
908
    return -1;
909

UNCOV
910
  if (fp)
×
911
  {
912
    /* recv mode */
913

UNCOV
914
    if (e && b->email && (b->encoding != ENC_BASE64) &&
×
UNCOV
915
        (b->encoding != ENC_QUOTED_PRINTABLE) && mutt_is_message_type(b->type, b->subtype))
×
916
    {
917
      /* message type attachments are written to mail folders. */
918

UNCOV
919
      char buf[8192] = { 0 };
×
UNCOV
920
      struct Message *msg = NULL;
×
921
      CopyHeaderFlags chflags = CH_NO_FLAGS;
922
      int rc = -1;
923

UNCOV
924
      struct Email *e_new = b->email;
×
925
      e_new->msgno = e->msgno; /* required for MH/maildir */
×
UNCOV
926
      e_new->read = true;
×
927

UNCOV
928
      if (!mutt_file_seek(fp, b->offset, SEEK_SET))
×
929
        return -1;
930
      if (!fgets(buf, sizeof(buf), fp))
×
931
        return -1;
932
      struct Mailbox *m_att = mx_path_resolve(path);
×
933
      if (!mx_mbox_open(m_att, MUTT_APPEND | MUTT_QUIET))
×
934
      {
935
        mailbox_free(&m_att);
×
936
        return -1;
×
937
      }
UNCOV
938
      msg = mx_msg_open_new(m_att, e_new,
×
939
                            is_from(buf, NULL, 0, NULL) ? MUTT_MSG_NO_FLAGS : MUTT_ADD_FROM);
×
940
      if (!msg)
×
941
      {
942
        mx_mbox_close(m_att);
×
UNCOV
943
        return -1;
×
944
      }
945
      if ((m_att->type == MUTT_MBOX) || (m_att->type == MUTT_MMDF))
×
946
        chflags = CH_FROM | CH_UPDATE_LEN;
UNCOV
947
      chflags |= ((m_att->type == MUTT_MAILDIR) ? CH_NOSTATUS : CH_UPDATE);
×
UNCOV
948
      if ((mutt_copy_message_fp(msg->fp, fp, e_new, MUTT_CM_NO_FLAGS, chflags, 0) == 0) &&
×
UNCOV
949
          (mx_msg_commit(m_att, msg) == 0))
×
950
      {
951
        rc = 0;
952
      }
953
      else
954
      {
955
        rc = -1;
956
      }
957

UNCOV
958
      mx_msg_close(m_att, &msg);
×
UNCOV
959
      mx_mbox_close(m_att);
×
UNCOV
960
      return rc;
×
961
    }
962
    else
963
    {
964
      /* In recv mode, extract from folder and decode */
965

966
      struct State state = { 0 };
×
967

968
      state.fp_out = save_attachment_open(path, opt);
×
969
      if (!state.fp_out)
×
970
      {
971
        mutt_perror("fopen");
×
UNCOV
972
        return -1;
×
973
      }
974
      if (!mutt_file_seek((state.fp_in = fp), b->offset, SEEK_SET))
×
975
      {
976
        mutt_file_fclose(&state.fp_out);
×
UNCOV
977
        return -1;
×
978
      }
UNCOV
979
      mutt_decode_attachment(b, &state);
×
980

981
      if (mutt_file_fsync_close(&state.fp_out) != 0)
×
982
      {
UNCOV
983
        mutt_perror("fclose");
×
UNCOV
984
        return -1;
×
985
      }
986
    }
987
  }
988
  else
989
  {
UNCOV
990
    if (!b->filename)
×
UNCOV
991
      return -1;
×
992

993
    /* In send mode, just copy file */
994

995
    FILE *fp_old = mutt_file_fopen(b->filename, "r");
×
996
    if (!fp_old)
×
997
    {
UNCOV
998
      mutt_perror("fopen");
×
999
      return -1;
×
1000
    }
1001

1002
    FILE *fp_new = save_attachment_open(path, opt);
×
1003
    if (!fp_new)
×
1004
    {
UNCOV
1005
      mutt_perror("fopen");
×
UNCOV
1006
      mutt_file_fclose(&fp_old);
×
1007
      return -1;
×
1008
    }
1009

1010
    if (mutt_file_copy_stream(fp_old, fp_new) == -1)
×
1011
    {
1012
      mutt_error(_("Write fault"));
×
UNCOV
1013
      mutt_file_fclose(&fp_old);
×
1014
      mutt_file_fclose(&fp_new);
×
1015
      return -1;
×
1016
    }
1017
    mutt_file_fclose(&fp_old);
×
1018
    if (mutt_file_fsync_close(&fp_new) != 0)
×
1019
    {
UNCOV
1020
      mutt_error(_("Write fault"));
×
UNCOV
1021
      return -1;
×
1022
    }
1023
  }
1024

1025
  return 0;
1026
}
1027

1028
/**
1029
 * mutt_decode_save_attachment - Decode, then save an attachment
1030
 * @param fp         File to read from (OPTIONAL)
1031
 * @param b          Attachment
1032
 * @param path       Path to save the Attachment to
1033
 * @param flags      Flags, e.g. #STATE_DISPLAY
1034
 * @param opt        Save option, see #SaveAttach
1035
 * @retval 0  Success
1036
 * @retval -1 Error
1037
 */
1038
int mutt_decode_save_attachment(FILE *fp, struct Body *b, const char *path,
×
1039
                                StateFlags flags, enum SaveAttach opt)
1040
{
UNCOV
1041
  struct State state = { 0 };
×
1042
  unsigned int saved_encoding = 0;
1043
  struct Body *saved_parts = NULL;
1044
  struct Email *e_saved = NULL;
1045
  int rc = 0;
1046

1047
  state.flags = flags;
×
1048

1049
  if (opt == MUTT_SAVE_APPEND)
×
UNCOV
1050
    state.fp_out = mutt_file_fopen_masked(path, "a");
×
1051
  else
UNCOV
1052
    state.fp_out = mutt_file_fopen_masked(path, "w");
×
1053

1054
  if (!state.fp_out)
×
1055
  {
UNCOV
1056
    mutt_perror("fopen");
×
1057
    return -1;
×
1058
  }
1059

1060
  if (fp)
×
1061
  {
UNCOV
1062
    state.fp_in = fp;
×
UNCOV
1063
    state.flags |= STATE_CHARCONV;
×
1064
  }
1065
  else
1066
  {
1067
    /* When called from the compose menu, the attachment isn't parsed,
1068
     * so we need to do it here. */
1069
    state.fp_in = mutt_file_fopen(b->filename, "r");
×
1070
    if (!state.fp_in)
×
1071
    {
UNCOV
1072
      mutt_perror("fopen");
×
UNCOV
1073
      mutt_file_fclose(&state.fp_out);
×
1074
      return -1;
×
1075
    }
1076

1077
    struct stat st = { 0 };
×
1078
    if (fstat(fileno(state.fp_in), &st) == -1)
×
1079
    {
1080
      mutt_perror("stat");
×
UNCOV
1081
      mutt_file_fclose(&state.fp_in);
×
UNCOV
1082
      mutt_file_fclose(&state.fp_out);
×
1083
      return -1;
×
1084
    }
1085

UNCOV
1086
    saved_encoding = b->encoding;
×
1087
    if (!is_multipart(b))
×
1088
      b->encoding = ENC_8BIT;
×
1089

1090
    b->length = st.st_size;
×
1091
    b->offset = 0;
×
UNCOV
1092
    saved_parts = b->parts;
×
1093
    e_saved = b->email;
×
1094
    mutt_parse_part(state.fp_in, b);
×
1095

UNCOV
1096
    if (b->noconv || is_multipart(b))
×
1097
      state.flags |= STATE_CHARCONV;
×
1098
  }
1099

UNCOV
1100
  mutt_body_handler(b, &state);
×
1101

UNCOV
1102
  if (mutt_file_fsync_close(&state.fp_out) != 0)
×
1103
  {
1104
    mutt_perror("fclose");
×
1105
    rc = -1;
1106
  }
1107
  if (!fp)
×
1108
  {
UNCOV
1109
    b->length = 0;
×
1110
    b->encoding = saved_encoding;
×
1111
    if (saved_parts)
×
1112
    {
UNCOV
1113
      email_free(&b->email);
×
1114
      b->parts = saved_parts;
×
UNCOV
1115
      b->email = e_saved;
×
1116
    }
UNCOV
1117
    mutt_file_fclose(&state.fp_in);
×
1118
  }
1119

1120
  return rc;
1121
}
1122

1123
/**
1124
 * mutt_print_attachment - Print out an attachment
1125
 * @param fp File to write to
1126
 * @param b  Attachment
1127
 * @retval 1 Success
1128
 * @retval 0 Error
1129
 *
1130
 * Ok, the difference between send and receive:
1131
 * recv: Body->filename is a suggested name, and Mailbox|Email points
1132
 *       to the attachment in mailbox which is encoded
1133
 * send: Body->filename points to the un-encoded file which contains the
1134
 *       attachment
1135
 */
UNCOV
1136
int mutt_print_attachment(FILE *fp, struct Body *b)
×
1137
{
UNCOV
1138
  char type[256] = { 0 };
×
1139
  pid_t pid;
1140
  FILE *fp_in = NULL, *fp_out = NULL;
×
1141
  bool unlink_newfile = false;
UNCOV
1142
  struct Buffer *newfile = buf_pool_get();
×
UNCOV
1143
  struct Buffer *cmd = buf_pool_get();
×
1144

1145
  int rc = 0;
1146

UNCOV
1147
  snprintf(type, sizeof(type), "%s/%s", BODY_TYPE(b), b->subtype);
×
1148

UNCOV
1149
  if (mailcap_lookup(b, type, sizeof(type), NULL, MUTT_MC_PRINT))
×
1150
  {
1151
    mutt_debug(LL_DEBUG2, "Using mailcap\n");
×
1152

1153
    struct MailcapEntry *entry = mailcap_entry_new();
×
UNCOV
1154
    mailcap_lookup(b, type, sizeof(type), entry, MUTT_MC_PRINT);
×
1155

1156
    char *sanitized_fname = mutt_str_dup(b->filename);
×
1157
    /* In send mode (!fp), we allow slashes because those are part of
1158
     * the tempfile.  The path will be removed in expand_filename */
UNCOV
1159
    mutt_file_sanitize_filename(sanitized_fname, fp ? true : false);
×
1160
    mailcap_expand_filename(entry->nametemplate, sanitized_fname, newfile);
×
UNCOV
1161
    FREE(&sanitized_fname);
×
1162

UNCOV
1163
    if (mutt_save_attachment(fp, b, buf_string(newfile), MUTT_SAVE_NO_FLAGS, NULL) == -1)
×
1164
    {
UNCOV
1165
      goto mailcap_cleanup;
×
1166
    }
1167
    unlink_newfile = 1;
1168

UNCOV
1169
    mutt_rfc3676_space_unstuff_attachment(b, buf_string(newfile));
×
1170

UNCOV
1171
    buf_strcpy(cmd, entry->printcommand);
×
1172

UNCOV
1173
    bool piped = mailcap_expand_command(b, buf_string(newfile), type, cmd);
×
1174

UNCOV
1175
    mutt_endwin();
×
1176

UNCOV
1177
    const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
×
1178
    /* interactive program */
1179
    if (piped)
×
1180
    {
1181
      fp_in = mutt_file_fopen(buf_string(newfile), "r");
×
1182
      if (!fp_in)
×
1183
      {
UNCOV
1184
        mutt_perror("fopen");
×
UNCOV
1185
        mailcap_entry_free(&entry);
×
1186
        goto mailcap_cleanup;
×
1187
      }
1188

1189
      pid = filter_create(buf_string(cmd), &fp_out, NULL, NULL, NeoMutt->env);
×
1190
      if (pid < 0)
×
1191
      {
1192
        mutt_perror(_("Can't create filter"));
×
UNCOV
1193
        mailcap_entry_free(&entry);
×
1194
        mutt_file_fclose(&fp_in);
×
1195
        goto mailcap_cleanup;
×
1196
      }
1197
      mutt_file_copy_stream(fp_in, fp_out);
×
1198
      mutt_file_fclose(&fp_out);
×
UNCOV
1199
      mutt_file_fclose(&fp_in);
×
UNCOV
1200
      if (filter_wait(pid) || c_wait_key)
×
UNCOV
1201
        mutt_any_key_to_continue(NULL);
×
1202
    }
1203
    else
1204
    {
UNCOV
1205
      int rc2 = mutt_system(buf_string(cmd));
×
1206
      if (rc2 == -1)
×
1207
        mutt_debug(LL_DEBUG1, "Error running \"%s\"\n", cmd->data);
×
1208

UNCOV
1209
      if ((rc2 != 0) || c_wait_key)
×
UNCOV
1210
        mutt_any_key_to_continue(NULL);
×
1211
    }
1212

1213
    rc = 1;
1214

1215
  mailcap_cleanup:
1216
    if (unlink_newfile)
1217
      mutt_file_unlink(buf_string(newfile));
×
1218

UNCOV
1219
    mailcap_entry_free(&entry);
×
1220
    goto out;
×
1221
  }
1222

1223
  const char *const c_print_command = cs_subset_string(NeoMutt->sub, "print_command");
×
1224
  if (mutt_istr_equal("text/plain", type) || mutt_istr_equal("application/postscript", type))
×
1225
  {
1226
    rc = (mutt_pipe_attachment(fp, b, NONULL(c_print_command), NULL));
×
UNCOV
1227
    goto out;
×
1228
  }
UNCOV
1229
  else if (mutt_can_decode(b))
×
1230
  {
1231
    /* decode and print */
1232

1233
    fp_in = NULL;
×
1234
    fp_out = NULL;
×
1235

UNCOV
1236
    buf_mktemp(newfile);
×
UNCOV
1237
    if (mutt_decode_save_attachment(fp, b, buf_string(newfile), STATE_PRINTING,
×
1238
                                    MUTT_SAVE_NO_FLAGS) == 0)
1239
    {
1240
      unlink_newfile = true;
1241
      mutt_debug(LL_DEBUG2, "successfully decoded %s type attachment to %s\n",
×
1242
                 type, buf_string(newfile));
1243

1244
      fp_in = mutt_file_fopen(buf_string(newfile), "r");
×
1245
      if (!fp_in)
×
1246
      {
UNCOV
1247
        mutt_perror("fopen");
×
1248
        goto decode_cleanup;
×
1249
      }
1250

1251
      mutt_debug(LL_DEBUG2, "successfully opened %s read-only\n", buf_string(newfile));
×
1252

UNCOV
1253
      mutt_endwin();
×
1254
      pid = filter_create(NONULL(c_print_command), &fp_out, NULL, NULL, NeoMutt->env);
×
1255
      if (pid < 0)
×
1256
      {
UNCOV
1257
        mutt_perror(_("Can't create filter"));
×
1258
        goto decode_cleanup;
×
1259
      }
1260

UNCOV
1261
      mutt_debug(LL_DEBUG2, "Filter created\n");
×
1262

1263
      mutt_file_copy_stream(fp_in, fp_out);
×
1264

1265
      mutt_file_fclose(&fp_out);
×
1266
      mutt_file_fclose(&fp_in);
×
1267

UNCOV
1268
      const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
×
UNCOV
1269
      if ((filter_wait(pid) != 0) || c_wait_key)
×
1270
        mutt_any_key_to_continue(NULL);
×
1271
      rc = 1;
1272
    }
1273
  decode_cleanup:
×
1274
    mutt_file_fclose(&fp_in);
×
UNCOV
1275
    mutt_file_fclose(&fp_out);
×
UNCOV
1276
    if (unlink_newfile)
×
UNCOV
1277
      mutt_file_unlink(buf_string(newfile));
×
1278
  }
1279
  else
1280
  {
UNCOV
1281
    mutt_error(_("I don't know how to print that"));
×
1282
    rc = 0;
1283
  }
1284

UNCOV
1285
out:
×
1286
  buf_pool_release(&newfile);
×
UNCOV
1287
  buf_pool_release(&cmd);
×
1288

UNCOV
1289
  return rc;
×
1290
}
1291

1292
/**
1293
 * mutt_add_temp_attachment - Add file to list of temporary attachments
1294
 * @param filename filename with full path
1295
 */
1296
void mutt_add_temp_attachment(const char *filename)
×
1297
{
UNCOV
1298
  struct AttachModuleData *md = neomutt_get_module_data(NeoMutt, MODULE_ID_ATTACH);
×
UNCOV
1299
  ASSERT(md);
×
1300

UNCOV
1301
  mutt_list_insert_tail(&md->temp_attachments, mutt_str_dup(filename));
×
UNCOV
1302
}
×
1303

1304
/**
1305
 * mutt_temp_attachments_cleanup - Delete all temporary attachments
1306
 */
1307
void mutt_temp_attachments_cleanup(void)
631✔
1308
{
1309
  struct AttachModuleData *md = neomutt_get_module_data(NeoMutt, MODULE_ID_ATTACH);
631✔
1310
  ASSERT(md);
631✔
1311

1312
  struct ListNode *np = NULL;
1313

1314
  STAILQ_FOREACH(np, &md->temp_attachments, entries)
631✔
1315
  {
UNCOV
1316
    (void) mutt_file_chmod_add(np->data, S_IWUSR);
×
UNCOV
1317
    mutt_file_unlink(np->data);
×
1318
  }
1319

1320
  mutt_list_free(&md->temp_attachments);
631✔
1321
}
631✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc