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

neomutt / neomutt / 25650723386

09 May 2026 09:51AM UTC coverage: 42.586% (-0.02%) from 42.608%
25650723386

push

github

flatcap
pager: fix broken message-hook

12459 of 29256 relevant lines covered (42.59%)

5222.64 hits per line

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

0.71
/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_NONE);
×
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
{
341
  struct AttachModuleData *mod_data = neomutt_get_module_data(NeoMutt, MODULE_ID_ATTACH);
×
342
  ASSERT(mod_data);
×
343

344
  struct ListNode *np = NULL;
345
  STAILQ_FOREACH(np, &mod_data->mime_lookup, entries)
×
346
  {
347
    const int i = (int) mutt_str_len(np->data) - 1;
×
348
    if (((i > 0) && (np->data[i - 1] == '/') && (np->data[i] == '*') &&
×
349
         mutt_istrn_equal(type, np->data, i)) ||
×
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 ||
×
355
          (n = mutt_lookup_mime_type(&tmp, b->description)) != TYPE_OTHER)
×
356
      {
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
      }
370
      FREE(&tmp.subtype);
×
371
      FREE(&tmp.xtype);
×
372
    }
373
  }
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
 */
389
static int wait_interactive_filter(pid_t pid)
×
390
{
391
  int rc;
392

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

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
 */
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 };
×
425
  char desc[512] = { 0 };
×
426
  char *fname = NULL;
×
427
  struct MailcapEntry *entry = NULL;
×
428
  int rc = -1;
429
  bool has_tempfile = false;
430
  bool unlink_pagerfile = false;
431

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

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) ||
×
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

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

452
  if (use_mailcap)
×
453
  {
454
    entry = mailcap_entry_new();
×
455
    enum MailcapLookup mailcap_opt = (mode == MUTT_VA_PAGER) ? MUTT_MC_AUTOVIEW : MUTT_MC_NONE;
×
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 */
461
        mailcap_entry_free(&entry);
×
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
      {
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."));
×
478
      goto return_error;
×
479
    }
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 */
485
    mutt_file_sanitize_filename(fname, fp ? true : false);
×
486
    mailcap_expand_filename(entry->nametemplate, fname, tempfile);
×
487
    FREE(&fname);
×
488

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);
×
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 */
508
        cid_save_attachments(related_ancestor->parts, &cid_map_list);
×
509
        /* replace Content-IDs with filenames */
510
        cid_to_filename(tempfile, &cid_map_list);
×
511
        /* empty Content-ID to filename mapping list */
512
        cid_map_list_clear(&cid_map_list);
×
513
      }
514
    }
515

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

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

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
    {
545
      if (use_pager &&
×
546
          ((fd_pager = mutt_file_open(buf_string(pagerfile),
×
547
                                      O_CREAT | O_EXCL | O_WRONLY, 0600)) == -1))
548
      {
549
        mutt_perror("open");
×
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)
×
557
          close(fd_pager);
×
558
        mutt_perror("open");
×
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,
×
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

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

578
      if (use_pager)
×
579
      {
580
        if (b->description)
×
581
        {
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
        }
590
        filter_wait(pid);
×
591
      }
592
      else
593
      {
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)
×
599
        close(fd_temp);
×
600
      if (fd_pager != -1)
×
601
        close(fd_pager);
×
602
    }
603
    else
604
    {
605
      /* interactive cmd */
606
      int rv = mutt_system(buf_string(cmd));
×
607
      if (rv == -1)
×
608
        mutt_debug(LL_DEBUG1, "Error running \"%s\"\n", cmd->data);
×
609

610
      if ((rv != 0) || (entry->needsterminal && c_wait_key))
×
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 */
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");
×
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;
×
638
        state.flags = STATE_CHARCONV;
×
639
        mutt_decode_attachment(b, &state);
×
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 */
647
        if (mutt_save_attachment(fp, b, buf_string(pagerfile), MUTT_SAVE_NONE, NULL))
×
648
          goto return_error;
×
649
        unlink_pagerfile = true;
650
      }
651
      mutt_rfc3676_space_unstuff_attachment(b, buf_string(pagerfile));
×
652
    }
653
    else
654
    {
655
      StateFlags flags = STATE_DISPLAY | STATE_DISPLAY_ATTACH;
656
      const char *const c_pager = pager_get_pager(NeoMutt->sub);
×
657
      if (!c_pager)
×
658
        flags |= STATE_PAGER;
659

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

668
    if (b->description)
×
669
      mutt_str_copy(desc, b->description, sizeof(desc));
×
670
    else if (b->filename)
×
671
      snprintf(desc, sizeof(desc), _("---Attachment: %s: %s"), b->filename, type);
×
672
    else
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;
×
684
    pdata.body = b;
×
685
    pdata.fname = buf_string(pagerfile);
×
686
    pdata.fp = fp;
×
687

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

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

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

704
return_error:
×
705

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

715
  mailcap_entry_free(&entry);
×
716

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

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

725
  return rc;
×
726
}
727

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

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

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

763
  mutt_endwin();
×
764

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

964
      struct State state = { 0 };
×
965

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

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

991
    /* In send mode, just copy file */
992

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

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

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

1023
  return 0;
1024
}
1025

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

1045
  state.flags = flags;
×
1046

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

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

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

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

1084
    saved_encoding = b->encoding;
×
1085
    if (!is_multipart(b))
×
1086
      b->encoding = ENC_8BIT;
×
1087

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

1094
    if (b->noconv || is_multipart(b))
×
1095
      state.flags |= STATE_CHARCONV;
×
1096
  }
1097

1098
  mutt_body_handler(b, &state);
×
1099

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

1118
  return rc;
1119
}
1120

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

1143
  int rc = 0;
1144

1145
  snprintf(type, sizeof(type), "%s/%s", BODY_TYPE(b), b->subtype);
×
1146

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

1151
    struct MailcapEntry *entry = mailcap_entry_new();
×
1152
    mailcap_lookup(b, type, sizeof(type), entry, MUTT_MC_PRINT);
×
1153

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

1161
    if (mutt_save_attachment(fp, b, buf_string(newfile), MUTT_SAVE_NONE, NULL) == -1)
×
1162
    {
1163
      goto mailcap_cleanup;
×
1164
    }
1165
    unlink_newfile = 1;
1166

1167
    mutt_rfc3676_space_unstuff_attachment(b, buf_string(newfile));
×
1168

1169
    buf_strcpy(cmd, entry->printcommand);
×
1170

1171
    bool piped = mailcap_expand_command(b, buf_string(newfile), type, cmd);
×
1172

1173
    mutt_endwin();
×
1174

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

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

1207
      if ((rc2 != 0) || c_wait_key)
×
1208
        mutt_any_key_to_continue(NULL);
×
1209
    }
1210

1211
    rc = 1;
1212

1213
  mailcap_cleanup:
1214
    if (unlink_newfile)
1215
      mutt_file_unlink(buf_string(newfile));
×
1216

1217
    mailcap_entry_free(&entry);
×
1218
    goto out;
×
1219
  }
1220

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

1231
    fp_in = NULL;
×
1232
    fp_out = NULL;
×
1233

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

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

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

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

1259
      mutt_debug(LL_DEBUG2, "Filter created\n");
×
1260

1261
      mutt_file_copy_stream(fp_in, fp_out);
×
1262

1263
      mutt_file_fclose(&fp_out);
×
1264
      mutt_file_fclose(&fp_in);
×
1265

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

1283
out:
×
1284
  buf_pool_release(&newfile);
×
1285
  buf_pool_release(&cmd);
×
1286

1287
  return rc;
×
1288
}
1289

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

1299
  mutt_list_insert_tail(&mod_data->temp_attachments, mutt_str_dup(filename));
×
1300
}
×
1301

1302
/**
1303
 * mutt_temp_attachments_cleanup - Delete all temporary attachments
1304
 * @param list List of temporary attachment files
1305
 */
1306
void mutt_temp_attachments_cleanup(struct ListHead *list)
651✔
1307
{
1308
  if (!list)
651✔
1309
    return;
1310

1311
  struct ListNode *np = NULL;
1312

1313
  STAILQ_FOREACH(np, list, entries)
651✔
1314
  {
1315
    (void) mutt_file_chmod_add(np->data, S_IWUSR);
×
1316
    mutt_file_unlink(np->data);
×
1317
  }
1318

1319
  mutt_list_free(list);
651✔
1320
}
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