Coverage Report

Created: 2023-06-07 06:15

/src/neomutt/pager/message.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Process a message for display in the pager
4
 *
5
 * @authors
6
 * Copyright (C) 2021 Richard Russon <rich@flatcap.org>
7
 *
8
 * @copyright
9
 * This program is free software: you can redistribute it and/or modify it under
10
 * the terms of the GNU General Public License as published by the Free Software
11
 * Foundation, either version 2 of the License, or (at your option) any later
12
 * version.
13
 *
14
 * This program is distributed in the hope that it will be useful, but WITHOUT
15
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17
 * details.
18
 *
19
 * You should have received a copy of the GNU General Public License along with
20
 * this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
/**
24
 * @page pager_message Process a message for display in the pager
25
 *
26
 * Process a message for display in the pager
27
 */
28
29
#include "config.h"
30
#include <errno.h>
31
#include <stdbool.h>
32
#include <stdio.h>
33
#include <unistd.h>
34
#include "mutt/lib.h"
35
#include "config/lib.h"
36
#include "email/lib.h"
37
#include "core/lib.h"
38
#include "gui/lib.h"
39
#include "mutt.h"
40
#include "attach/lib.h"
41
#include "index/lib.h"
42
#include "menu/lib.h"
43
#include "ncrypt/lib.h"
44
#include "pager/lib.h"
45
#include "question/lib.h"
46
#include "copy.h"
47
#include "format_flags.h"
48
#include "globals.h" // IWYU pragma: keep
49
#include "hdrline.h"
50
#include "hook.h"
51
#include "keymap.h"
52
#include "mview.h"
53
#include "mx.h"
54
#include "protos.h"
55
#ifdef USE_AUTOCRYPT
56
#include "autocrypt/lib.h"
57
#endif
58
59
/// Status bar message when entire message is visible in the Pager
60
static const char *ExtPagerProgress = N_("all");
61
62
/**
63
 * process_protected_headers - Get the protected header and update the index
64
 * @param m Mailbox
65
 * @param e Email to update
66
 */
67
static void process_protected_headers(struct Mailbox *m, struct Email *e)
68
0
{
69
0
  struct Envelope *prot_headers = NULL;
70
0
  regmatch_t pmatch[1];
71
72
0
  const bool c_crypt_protected_headers_read = cs_subset_bool(NeoMutt->sub, "crypt_protected_headers_read");
73
#ifdef USE_AUTOCRYPT
74
  const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
75
  if (!c_crypt_protected_headers_read && !c_autocrypt)
76
    return;
77
#else
78
0
  if (!c_crypt_protected_headers_read)
79
0
    return;
80
0
#endif
81
82
  /* Grab protected headers to update in the index */
83
0
  if (e->security & SEC_SIGN)
84
0
  {
85
    /* Don't update on a bad signature.
86
     *
87
     * This is a simplification.  It's possible the headers are in the
88
     * encrypted part of a nested encrypt/signed.  But properly handling that
89
     * case would require more complexity in the decryption handlers, which
90
     * I'm not sure is worth it. */
91
0
    if (!(e->security & SEC_GOODSIGN))
92
0
      return;
93
94
0
    if (mutt_is_multipart_signed(e->body) && e->body->parts)
95
0
    {
96
0
      prot_headers = e->body->parts->mime_headers;
97
0
    }
98
0
    else if (((WithCrypto & APPLICATION_SMIME) != 0) && mutt_is_application_smime(e->body))
99
0
    {
100
0
      prot_headers = e->body->mime_headers;
101
0
    }
102
0
  }
103
0
  if (!prot_headers && (e->security & SEC_ENCRYPT))
104
0
  {
105
0
    if (((WithCrypto & APPLICATION_PGP) != 0) &&
106
0
        (mutt_is_valid_multipart_pgp_encrypted(e->body) ||
107
0
         mutt_is_malformed_multipart_pgp_encrypted(e->body)))
108
0
    {
109
0
      prot_headers = e->body->mime_headers;
110
0
    }
111
0
    else if (((WithCrypto & APPLICATION_SMIME) != 0) && mutt_is_application_smime(e->body))
112
0
    {
113
0
      prot_headers = e->body->mime_headers;
114
0
    }
115
0
  }
116
117
  /* Update protected headers in the index and header cache. */
118
0
  if (c_crypt_protected_headers_read && prot_headers && prot_headers->subject &&
119
0
      !mutt_str_equal(e->env->subject, prot_headers->subject))
120
0
  {
121
0
    if (m->subj_hash && e->env->real_subj)
122
0
      mutt_hash_delete(m->subj_hash, e->env->real_subj, e);
123
124
0
    mutt_str_replace(&e->env->subject, prot_headers->subject);
125
0
    FREE(&e->env->disp_subj);
126
0
    const struct Regex *c_reply_regex = cs_subset_regex(NeoMutt->sub, "reply_regex");
127
0
    if (mutt_regex_capture(c_reply_regex, e->env->subject, 1, pmatch))
128
0
    {
129
0
      e->env->real_subj = e->env->subject + pmatch[0].rm_eo;
130
0
      if (e->env->real_subj[0] == '\0')
131
0
        e->env->real_subj = NULL;
132
0
    }
133
0
    else
134
0
    {
135
0
      e->env->real_subj = e->env->subject;
136
0
    }
137
138
0
    if (m->subj_hash)
139
0
      mutt_hash_insert(m->subj_hash, e->env->real_subj, e);
140
141
0
    mx_save_hcache(m, e);
142
143
    /* Also persist back to the message headers if this is set */
144
0
    const bool c_crypt_protected_headers_save = cs_subset_bool(NeoMutt->sub, "crypt_protected_headers_save");
145
0
    if (c_crypt_protected_headers_save)
146
0
    {
147
0
      e->env->changed |= MUTT_ENV_CHANGED_SUBJECT;
148
0
      e->changed = true;
149
0
      m->changed = true;
150
0
    }
151
0
  }
152
153
#ifdef USE_AUTOCRYPT
154
  if (c_autocrypt && (e->security & SEC_ENCRYPT) && prot_headers && prot_headers->autocrypt_gossip)
155
  {
156
    mutt_autocrypt_process_gossip_header(e, prot_headers);
157
  }
158
#endif
159
0
}
160
161
/**
162
 * email_to_file - Decrypt, decode and weed an Email into a file
163
 * @param msg      Raw Email
164
 * @param tempfile Temporary filename for result
165
 * @param m        Mailbox
166
 * @param e        Email to display
167
 * @param header   Header to prefix output (OPTIONAL)
168
 * @param wrap_len Width to wrap lines
169
 * @param cmflags  Message flags, e.g. #MUTT_CM_DECODE
170
 * @retval  0 Success
171
 * @retval -1 Error
172
 *
173
 * @note Flags may be added to @a cmflags
174
 */
175
static int email_to_file(struct Message *msg, struct Buffer *tempfile,
176
                         struct Mailbox *m, struct Email *e, const char *header,
177
                         int wrap_len, CopyMessageFlags *cmflags)
178
0
{
179
0
  int rc = 0;
180
0
  pid_t filterpid = -1;
181
182
0
  mutt_parse_mime_message(e, msg->fp);
183
0
  mutt_message_hook(m, e, MUTT_MESSAGE_HOOK);
184
185
0
  char columns[16] = { 0 };
186
  // win_pager might not be visible and have a size yet, so use win_index
187
0
  snprintf(columns, sizeof(columns), "%d", wrap_len);
188
0
  envlist_set(&EnvList, "COLUMNS", columns, true);
189
190
  /* see if crypto is needed for this message.  if so, we should exit curses */
191
0
  if ((WithCrypto != 0) && e->security)
192
0
  {
193
0
    if (e->security & SEC_ENCRYPT)
194
0
    {
195
0
      if (e->security & APPLICATION_SMIME)
196
0
        crypt_smime_getkeys(e->env);
197
0
      if (!crypt_valid_passphrase(e->security))
198
0
        goto cleanup;
199
200
0
      *cmflags |= MUTT_CM_VERIFY;
201
0
    }
202
0
    else if (e->security & SEC_SIGN)
203
0
    {
204
      /* find out whether or not the verify signature */
205
      /* L10N: Used for the $crypt_verify_sig prompt */
206
0
      const enum QuadOption c_crypt_verify_sig = cs_subset_quad(NeoMutt->sub, "crypt_verify_sig");
207
0
      if (query_quadoption(c_crypt_verify_sig, _("Verify signature?")) == MUTT_YES)
208
0
      {
209
0
        *cmflags |= MUTT_CM_VERIFY;
210
0
      }
211
0
    }
212
0
  }
213
214
0
  if (*cmflags & MUTT_CM_VERIFY || e->security & SEC_ENCRYPT)
215
0
  {
216
0
    if (e->security & APPLICATION_PGP)
217
0
    {
218
0
      if (!TAILQ_EMPTY(&e->env->from))
219
0
        crypt_pgp_invoke_getkeys(TAILQ_FIRST(&e->env->from));
220
221
0
      crypt_invoke_message(APPLICATION_PGP);
222
0
    }
223
224
0
    if (e->security & APPLICATION_SMIME)
225
0
      crypt_invoke_message(APPLICATION_SMIME);
226
0
  }
227
228
0
  FILE *fp_filter_out = NULL;
229
0
  buf_mktemp(tempfile);
230
0
  FILE *fp_out = mutt_file_fopen(buf_string(tempfile), "w");
231
0
  if (!fp_out)
232
0
  {
233
0
    mutt_error(_("Could not create temporary file"));
234
0
    goto cleanup;
235
0
  }
236
237
0
  const char *const c_display_filter = cs_subset_string(NeoMutt->sub, "display_filter");
238
0
  if (c_display_filter)
239
0
  {
240
0
    fp_filter_out = fp_out;
241
0
    fp_out = NULL;
242
0
    filterpid = filter_create_fd(c_display_filter, &fp_out, NULL, NULL, -1,
243
0
                                 fileno(fp_filter_out), -1);
244
0
    if (filterpid < 0)
245
0
    {
246
0
      mutt_error(_("Can't create display filter"));
247
0
      mutt_file_fclose(&fp_filter_out);
248
0
      unlink(buf_string(tempfile));
249
0
      goto cleanup;
250
0
    }
251
0
  }
252
253
0
  if (header)
254
0
  {
255
0
    fputs(header, fp_out);
256
0
    fputs("\n\n", fp_out);
257
0
  }
258
259
0
  const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
260
0
  CopyHeaderFlags chflags = (c_weed ? (CH_WEED | CH_REORDER) : CH_NO_FLAGS) |
261
0
                            CH_DECODE | CH_FROM | CH_DISPLAY;
262
#ifdef USE_NOTMUCH
263
  if (m->type == MUTT_NOTMUCH)
264
    chflags |= CH_VIRTUAL;
265
#endif
266
0
  rc = mutt_copy_message(fp_out, e, msg, *cmflags, chflags, wrap_len);
267
268
0
  if (((mutt_file_fclose(&fp_out) != 0) && (errno != EPIPE)) || (rc < 0))
269
0
  {
270
0
    mutt_error(_("Could not copy message"));
271
0
    if (fp_filter_out)
272
0
    {
273
0
      filter_wait(filterpid);
274
0
      mutt_file_fclose(&fp_filter_out);
275
0
    }
276
0
    mutt_file_unlink(buf_string(tempfile));
277
0
    goto cleanup;
278
0
  }
279
280
0
  if (fp_filter_out && (filter_wait(filterpid) != 0))
281
0
    mutt_any_key_to_continue(NULL);
282
283
0
  mutt_file_fclose(&fp_filter_out); /* XXX - check result? */
284
285
0
  if (WithCrypto)
286
0
  {
287
    /* update crypto information for this message */
288
0
    e->security &= ~(SEC_GOODSIGN | SEC_BADSIGN);
289
0
    e->security |= crypt_query(e->body);
290
291
    /* Remove color cache for this message, in case there
292
     * are color patterns for both ~g and ~V */
293
0
    e->attr_color = NULL;
294
295
    /* Process protected headers and autocrypt gossip headers */
296
0
    process_protected_headers(m, e);
297
0
  }
298
299
0
cleanup:
300
0
  envlist_unset(&EnvList, "COLUMNS");
301
0
  return rc;
302
0
}
303
304
/**
305
 * external_pager - Display a message in an external program
306
 * @param mv      Mailbox view
307
 * @param e       Email to display
308
 * @param command External command to run
309
 * @retval  0 Success
310
 * @retval -1 Error
311
 */
312
int external_pager(struct MailboxView *mv, struct Email *e, const char *command)
313
0
{
314
0
  if (!mv || !mv->mailbox)
315
0
    return -1;
316
317
0
  struct Mailbox *m = mv->mailbox;
318
0
  struct Message *msg = mx_msg_open(m, e);
319
0
  if (!msg)
320
0
    return -1;
321
322
0
  char buf[1024] = { 0 };
323
0
  const char *const c_pager_format = cs_subset_string(NeoMutt->sub, "pager_format");
324
0
  const int screen_width = RootWindow->state.cols;
325
0
  mutt_make_string(buf, sizeof(buf), screen_width, NONULL(c_pager_format), m,
326
0
                   -1, e, MUTT_FORMAT_NO_FLAGS, _(ExtPagerProgress));
327
328
0
  struct Buffer *tempfile = buf_pool_get();
329
330
0
  CopyMessageFlags cmflags = MUTT_CM_DECODE | MUTT_CM_DISPLAY | MUTT_CM_CHARCONV;
331
0
  int rc = email_to_file(msg, tempfile, m, e, buf, screen_width, &cmflags);
332
0
  if (rc < 0)
333
0
    goto cleanup;
334
335
0
  mutt_endwin();
336
337
0
  struct Buffer *cmd = buf_pool_get();
338
0
  buf_printf(cmd, "%s %s", command, buf_string(tempfile));
339
0
  int r = mutt_system(buf_string(cmd));
340
0
  if (r == -1)
341
0
    mutt_error(_("Error running \"%s\""), buf_string(cmd));
342
0
  unlink(buf_string(tempfile));
343
0
  buf_pool_release(&cmd);
344
345
0
  if (!OptNoCurses)
346
0
    keypad(stdscr, true);
347
0
  if (r != -1)
348
0
    mutt_set_flag(m, e, MUTT_READ, true, true);
349
0
  const bool c_prompt_after = cs_subset_bool(NeoMutt->sub, "prompt_after");
350
0
  if ((r != -1) && c_prompt_after)
351
0
  {
352
0
    mutt_unget_ch(mutt_any_key_to_continue(_("Command: ")));
353
0
    rc = km_dokey(MENU_PAGER);
354
0
  }
355
0
  else
356
0
  {
357
0
    rc = 0;
358
0
  }
359
360
0
cleanup:
361
0
  mx_msg_close(m, &msg);
362
0
  buf_pool_release(&tempfile);
363
0
  return rc;
364
0
}
365
366
/**
367
 * notify_crypto - Notify the user about the crypto status of the Email
368
 * @param e       Email to display
369
 * @param msg     Raw Email
370
 * @param cmflags Message flags, e.g. #MUTT_CM_DECODE
371
 */
372
static void notify_crypto(struct Email *e, struct Message *msg, CopyMessageFlags cmflags)
373
0
{
374
0
  if ((WithCrypto != 0) && (e->security & APPLICATION_SMIME) && (cmflags & MUTT_CM_VERIFY))
375
0
  {
376
0
    if (e->security & SEC_GOODSIGN)
377
0
    {
378
0
      if (crypt_smime_verify_sender(e, msg) == 0)
379
0
        mutt_message(_("S/MIME signature successfully verified"));
380
0
      else
381
0
        mutt_error(_("S/MIME certificate owner does not match sender"));
382
0
    }
383
0
    else if (e->security & SEC_PARTSIGN)
384
0
    {
385
0
      mutt_message(_("Warning: Part of this message has not been signed"));
386
0
    }
387
0
    else if (e->security & SEC_SIGN || e->security & SEC_BADSIGN)
388
0
    {
389
0
      mutt_error(_("S/MIME signature could NOT be verified"));
390
0
    }
391
0
  }
392
393
0
  if ((WithCrypto != 0) && (e->security & APPLICATION_PGP) && (cmflags & MUTT_CM_VERIFY))
394
0
  {
395
0
    if (e->security & SEC_GOODSIGN)
396
0
      mutt_message(_("PGP signature successfully verified"));
397
0
    else if (e->security & SEC_PARTSIGN)
398
0
      mutt_message(_("Warning: Part of this message has not been signed"));
399
0
    else if (e->security & SEC_SIGN)
400
0
      mutt_message(_("PGP signature could NOT be verified"));
401
0
  }
402
0
}
403
404
/**
405
 * squash_index_panel - Shrink or hide the Index Panel
406
 * @param m      Mailbox
407
 * @param win_index Index Window
408
 * @param win_pager Pager Window
409
 */
410
static void squash_index_panel(struct Mailbox *m, struct MuttWindow *win_index,
411
                               struct MuttWindow *win_pager)
412
0
{
413
0
  const short c_pager_index_lines = cs_subset_number(NeoMutt->sub, "pager_index_lines");
414
0
  if (c_pager_index_lines > 0)
415
0
  {
416
0
    win_index->size = MUTT_WIN_SIZE_FIXED;
417
0
    win_index->req_rows = c_pager_index_lines;
418
0
    win_index->parent->size = MUTT_WIN_SIZE_MINIMISE;
419
0
  }
420
0
  window_set_visible(win_index->parent, (c_pager_index_lines > 0));
421
422
0
  window_set_visible(win_pager->parent, true);
423
424
0
  struct MuttWindow *dlg = dialog_find(win_index);
425
0
  mutt_window_reflow(dlg);
426
427
  // Force the menu to reframe itself
428
0
  struct Menu *menu = win_index->wdata;
429
0
  menu_set_index(menu, menu_get_index(menu));
430
0
}
431
432
/**
433
 * expand_index_panel - Restore the Index Panel
434
 * @param win_index Index Window
435
 * @param win_pager Pager Window
436
 */
437
static void expand_index_panel(struct MuttWindow *win_index, struct MuttWindow *win_pager)
438
0
{
439
0
  win_index->size = MUTT_WIN_SIZE_MAXIMISE;
440
0
  win_index->req_rows = MUTT_WIN_SIZE_UNLIMITED;
441
0
  win_index->parent->size = MUTT_WIN_SIZE_MAXIMISE;
442
0
  win_index->parent->req_rows = MUTT_WIN_SIZE_UNLIMITED;
443
0
  window_set_visible(win_index->parent, true);
444
445
0
  window_set_visible(win_pager->parent, false);
446
447
0
  struct MuttWindow *dlg = dialog_find(win_index);
448
0
  mutt_window_reflow(dlg);
449
0
}
450
451
/**
452
 * mutt_display_message - Display a message in the pager
453
 * @param win_index Index Window
454
 * @param shared    Shared Index data
455
 * @retval  0 Success
456
 * @retval -1 Error
457
 */
458
int mutt_display_message(struct MuttWindow *win_index, struct IndexSharedData *shared)
459
0
{
460
0
  struct MuttWindow *dlg = dialog_find(win_index);
461
0
  struct MuttWindow *win_pager = window_find_child(dlg, WT_CUSTOM);
462
0
  struct MuttWindow *win_pbar = window_find_child(dlg, WT_STATUS_BAR);
463
0
  struct Buffer *tempfile = buf_pool_get();
464
0
  struct Message *msg = NULL;
465
466
0
  squash_index_panel(shared->mailbox, win_index, win_pager);
467
468
0
  int rc = PAGER_LOOP_QUIT;
469
0
  do
470
0
  {
471
0
    msg = mx_msg_open(shared->mailbox, shared->email);
472
0
    if (!msg)
473
0
      break;
474
475
0
    CopyMessageFlags cmflags = MUTT_CM_DECODE | MUTT_CM_DISPLAY | MUTT_CM_CHARCONV;
476
477
0
    buf_reset(tempfile);
478
    // win_pager might not be visible and have a size yet, so use win_index
479
0
    rc = email_to_file(msg, tempfile, shared->mailbox, shared->email, NULL,
480
0
                       win_index->state.cols, &cmflags);
481
0
    if (rc < 0)
482
0
      break;
483
484
0
    notify_crypto(shared->email, msg, cmflags);
485
486
    /* Invoke the builtin pager */
487
0
    struct PagerData pdata = { 0 };
488
0
    struct PagerView pview = { &pdata };
489
490
0
    pdata.fp = msg->fp;
491
0
    pdata.fname = buf_string(tempfile);
492
493
0
    pview.mode = PAGER_MODE_EMAIL;
494
0
    pview.banner = NULL;
495
0
    pview.flags = MUTT_PAGER_MESSAGE |
496
0
                  (shared->email->body->nowrap ? MUTT_PAGER_NOWRAP : 0);
497
0
    pview.win_index = win_index;
498
0
    pview.win_pbar = win_pbar;
499
0
    pview.win_pager = win_pager;
500
501
0
    rc = mutt_pager(&pview);
502
0
    mx_msg_close(shared->mailbox, &msg);
503
0
  } while (rc == PAGER_LOOP_RELOAD);
504
505
0
  expand_index_panel(win_index, win_pager);
506
507
0
  mx_msg_close(shared->mailbox, &msg);
508
0
  buf_pool_release(&tempfile);
509
0
  return rc;
510
0
}