Coverage Report

Created: 2023-06-07 06:15

/src/neomutt/postpone/postpone.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Postponed Email Selection Dialog
4
 *
5
 * @authors
6
 * Copyright (C) 1996-2002,2012-2013 Michael R. Elkins <me@mutt.org>
7
 * Copyright (C) 1999-2002,2004 Thomas Roessler <roessler@does-not-exist.org>
8
 *
9
 * @copyright
10
 * This program is free software: you can redistribute it and/or modify it under
11
 * the terms of the GNU General Public License as published by the Free Software
12
 * Foundation, either version 2 of the License, or (at your option) any later
13
 * version.
14
 *
15
 * This program is distributed in the hope that it will be useful, but WITHOUT
16
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18
 * details.
19
 *
20
 * You should have received a copy of the GNU General Public License along with
21
 * this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
/**
25
 * @page postpone_postpone Postponed Email
26
 *
27
 * Functions to deal with Postponed Emails.
28
 */
29
30
#include "config.h"
31
#include <stdbool.h>
32
#include <stdio.h>
33
#include <string.h>
34
#include <sys/stat.h>
35
#include <unistd.h>
36
#include "mutt/lib.h"
37
#include "address/lib.h"
38
#include "config/lib.h"
39
#include "email/lib.h"
40
#include "core/lib.h"
41
#include "mutt.h"
42
#include "lib.h"
43
#include "ncrypt/lib.h"
44
#include "send/lib.h"
45
#include "globals.h"
46
#include "handler.h"
47
#include "mutt_logging.h"
48
#include "mutt_thread.h"
49
#include "muttlib.h"
50
#include "mx.h"
51
#include "protos.h"
52
#include "rfc3676.h"
53
#ifdef USE_IMAP
54
#include "imap/lib.h"
55
#endif
56
57
/// Number of postponed (draft) emails
58
short PostCount = 0;
59
/// When true, force a recount of the postponed (draft) emails
60
static bool UpdateNumPostponed = false;
61
62
/**
63
 * mutt_num_postponed - Return the number of postponed messages
64
 * @param m    currently selected mailbox
65
 * @param force
66
 * * false Use a cached value if costly to get a fresh count (IMAP)
67
 * * true Force check
68
 * @retval num Postponed messages
69
 */
70
int mutt_num_postponed(struct Mailbox *m, bool force)
71
0
{
72
0
  struct stat st = { 0 };
73
74
0
  static time_t LastModify = 0;
75
0
  static char *OldPostponed = NULL;
76
77
0
  if (UpdateNumPostponed)
78
0
  {
79
0
    UpdateNumPostponed = false;
80
0
    force = true;
81
0
  }
82
83
0
  const char *const c_postponed = cs_subset_string(NeoMutt->sub, "postponed");
84
0
  if (!mutt_str_equal(c_postponed, OldPostponed))
85
0
  {
86
0
    FREE(&OldPostponed);
87
0
    OldPostponed = mutt_str_dup(c_postponed);
88
0
    LastModify = 0;
89
0
    force = true;
90
0
  }
91
92
0
  if (!c_postponed)
93
0
    return 0;
94
95
  // We currently are in the `$postponed` mailbox so just pick the current status
96
0
  if (m && mutt_str_equal(c_postponed, m->realpath))
97
0
  {
98
0
    PostCount = m->msg_count - m->msg_deleted;
99
0
    return PostCount;
100
0
  }
101
102
0
#ifdef USE_IMAP
103
  /* LastModify is useless for IMAP */
104
0
  if (imap_path_probe(c_postponed, NULL) == MUTT_IMAP)
105
0
  {
106
0
    if (force)
107
0
    {
108
0
      short newpc;
109
110
0
      newpc = imap_path_status(c_postponed, false);
111
0
      if (newpc >= 0)
112
0
      {
113
0
        PostCount = newpc;
114
0
        mutt_debug(LL_DEBUG3, "%d postponed IMAP messages found\n", PostCount);
115
0
      }
116
0
      else
117
0
      {
118
0
        mutt_debug(LL_DEBUG3, "using old IMAP postponed count\n");
119
0
      }
120
0
    }
121
0
    return PostCount;
122
0
  }
123
0
#endif
124
125
0
  if (stat(c_postponed, &st) == -1)
126
0
  {
127
0
    PostCount = 0;
128
0
    LastModify = 0;
129
0
    return 0;
130
0
  }
131
132
0
  if (S_ISDIR(st.st_mode))
133
0
  {
134
    /* if we have a maildir mailbox, we need to stat the "new" dir */
135
0
    struct Buffer *buf = buf_pool_get();
136
137
0
    buf_printf(buf, "%s/new", c_postponed);
138
0
    if ((access(buf_string(buf), F_OK) == 0) && (stat(buf_string(buf), &st) == -1))
139
0
    {
140
0
      PostCount = 0;
141
0
      LastModify = 0;
142
0
      buf_pool_release(&buf);
143
0
      return 0;
144
0
    }
145
0
    buf_pool_release(&buf);
146
0
  }
147
148
0
  if (LastModify < st.st_mtime)
149
0
  {
150
0
#ifdef USE_NNTP
151
0
    int optnews = OptNews;
152
0
#endif
153
0
    LastModify = st.st_mtime;
154
155
0
    if (access(c_postponed, R_OK | F_OK) != 0)
156
0
      return PostCount = 0;
157
0
#ifdef USE_NNTP
158
0
    if (optnews)
159
0
      OptNews = false;
160
0
#endif
161
0
    struct Mailbox *m_post = mx_path_resolve(c_postponed);
162
0
    if (mx_mbox_open(m_post, MUTT_NOSORT | MUTT_QUIET))
163
0
    {
164
0
      PostCount = m_post->msg_count;
165
0
      mx_fastclose_mailbox(m_post, false);
166
0
    }
167
0
    else
168
0
    {
169
0
      PostCount = 0;
170
0
    }
171
0
    mailbox_free(&m_post);
172
173
0
#ifdef USE_NNTP
174
0
    if (optnews)
175
0
      OptNews = true;
176
0
#endif
177
0
  }
178
179
0
  return PostCount;
180
0
}
181
182
/**
183
 * mutt_update_num_postponed - Force the update of the number of postponed messages
184
 */
185
void mutt_update_num_postponed(void)
186
0
{
187
0
  UpdateNumPostponed = true;
188
0
}
189
190
/**
191
 * hardclose - Try hard to close a mailbox
192
 * @param m Mailbox to close
193
 */
194
static void hardclose(struct Mailbox *m)
195
0
{
196
  /* messages might have been marked for deletion.
197
   * try once more on reopen before giving up. */
198
0
  enum MxStatus rc = mx_mbox_close(m);
199
0
  if (rc != MX_STATUS_ERROR && rc != MX_STATUS_OK)
200
0
    rc = mx_mbox_close(m);
201
0
  if (rc != MX_STATUS_OK)
202
0
    mx_fastclose_mailbox(m, false);
203
0
}
204
205
/**
206
 * mutt_parse_crypt_hdr - Parse a crypto header string
207
 * @param p                Header string to parse
208
 * @param set_empty_signas Allow an empty "Sign as"
209
 * @param crypt_app App, e.g. #APPLICATION_PGP
210
 * @retval num SecurityFlags, see #SecurityFlags
211
 */
212
SecurityFlags mutt_parse_crypt_hdr(const char *p, bool set_empty_signas, SecurityFlags crypt_app)
213
0
{
214
0
  char smime_cryptalg[1024] = { 0 };
215
0
  char sign_as[1024] = { 0 };
216
0
  char *q = NULL;
217
0
  SecurityFlags flags = SEC_NO_FLAGS;
218
219
0
  if (!WithCrypto)
220
0
    return SEC_NO_FLAGS;
221
222
0
  p = mutt_str_skip_email_wsp(p);
223
0
  for (; p[0] != '\0'; p++)
224
0
  {
225
0
    switch (p[0])
226
0
    {
227
0
      case 'c':
228
0
      case 'C':
229
0
        q = smime_cryptalg;
230
231
0
        if (p[1] == '<')
232
0
        {
233
0
          for (p += 2; (p[0] != '\0') && (p[0] != '>') &&
234
0
                       (q < (smime_cryptalg + sizeof(smime_cryptalg) - 1));
235
0
               *q++ = *p++)
236
0
          {
237
0
          }
238
239
0
          if (p[0] != '>')
240
0
          {
241
0
            mutt_error(_("Illegal S/MIME header"));
242
0
            return SEC_NO_FLAGS;
243
0
          }
244
0
        }
245
246
0
        *q = '\0';
247
0
        break;
248
249
0
      case 'e':
250
0
      case 'E':
251
0
        flags |= SEC_ENCRYPT;
252
0
        break;
253
254
0
      case 'i':
255
0
      case 'I':
256
0
        flags |= SEC_INLINE;
257
0
        break;
258
259
      /* This used to be the micalg parameter.
260
       *
261
       * It's no longer needed, so we just skip the parameter in order
262
       * to be able to recall old messages.  */
263
0
      case 'm':
264
0
      case 'M':
265
0
        if (p[1] != '<')
266
0
          break;
267
268
0
        for (p += 2; (p[0] != '\0') && (p[0] != '>'); p++)
269
0
          ; // do nothing
270
271
0
        if (p[0] != '>')
272
0
        {
273
0
          mutt_error(_("Illegal crypto header"));
274
0
          return SEC_NO_FLAGS;
275
0
        }
276
0
        break;
277
278
0
      case 'o':
279
0
      case 'O':
280
0
        flags |= SEC_OPPENCRYPT;
281
0
        break;
282
283
0
      case 'a':
284
0
      case 'A':
285
#ifdef USE_AUTOCRYPT
286
        flags |= SEC_AUTOCRYPT;
287
#endif
288
0
        break;
289
290
0
      case 'z':
291
0
      case 'Z':
292
#ifdef USE_AUTOCRYPT
293
        flags |= SEC_AUTOCRYPT_OVERRIDE;
294
#endif
295
0
        break;
296
297
0
      case 's':
298
0
      case 'S':
299
0
        flags |= SEC_SIGN;
300
0
        q = sign_as;
301
302
0
        if (p[1] == '<')
303
0
        {
304
0
          for (p += 2;
305
0
               (p[0] != '\0') && (*p != '>') && (q < (sign_as + sizeof(sign_as) - 1));
306
0
               *q++ = *p++)
307
0
          {
308
0
          }
309
310
0
          if (p[0] != '>')
311
0
          {
312
0
            mutt_error(_("Illegal crypto header"));
313
0
            return SEC_NO_FLAGS;
314
0
          }
315
0
        }
316
317
0
        q[0] = '\0';
318
0
        break;
319
320
0
      default:
321
0
        mutt_error(_("Illegal crypto header"));
322
0
        return SEC_NO_FLAGS;
323
0
    }
324
0
  }
325
326
  /* the cryptalg field must not be empty */
327
0
  if (((WithCrypto & APPLICATION_SMIME) != 0) && *smime_cryptalg)
328
0
  {
329
0
    struct Buffer errmsg = buf_make(0);
330
0
    int rc = cs_subset_str_string_set(NeoMutt->sub, "smime_encrypt_with",
331
0
                                      smime_cryptalg, &errmsg);
332
333
0
    if ((CSR_RESULT(rc) != CSR_SUCCESS) && !buf_is_empty(&errmsg))
334
0
      mutt_error("%s", buf_string(&errmsg));
335
336
0
    buf_dealloc(&errmsg);
337
0
  }
338
339
  /* Set {Smime,Pgp}SignAs, if desired. */
340
341
0
  if (((WithCrypto & APPLICATION_PGP) != 0) && (crypt_app == APPLICATION_PGP) &&
342
0
      (flags & SEC_SIGN) && (set_empty_signas || *sign_as))
343
0
  {
344
0
    cs_subset_str_string_set(NeoMutt->sub, "pgp_sign_as", sign_as, NULL);
345
0
  }
346
347
0
  if (((WithCrypto & APPLICATION_SMIME) != 0) && (crypt_app == APPLICATION_SMIME) &&
348
0
      (flags & SEC_SIGN) && (set_empty_signas || *sign_as))
349
0
  {
350
0
    cs_subset_str_string_set(NeoMutt->sub, "smime_sign_as", sign_as, NULL);
351
0
  }
352
353
0
  return flags;
354
0
}
355
356
/**
357
 * create_tmp_files_for_attachments - Create temporary files for all attachments
358
 * @param fp_body           file containing the template
359
 * @param file              Allocated buffer for temporary file name
360
 * @param e_new             The new email template header
361
 * @param body              First body in email or group
362
 * @param protected_headers MIME headers for email template
363
 * @retval  0 Success
364
 * @retval -1 Error
365
 */
366
static int create_tmp_files_for_attachments(FILE *fp_body, struct Buffer *file,
367
                                            struct Email *e_new, struct Body *body,
368
                                            struct Envelope *protected_headers)
369
0
{
370
0
  struct Body *b = NULL;
371
0
  struct State state = { 0 };
372
373
0
  state.fp_in = fp_body;
374
375
0
  for (b = body; b; b = b->next)
376
0
  {
377
0
    if (b->type == TYPE_MULTIPART)
378
0
    {
379
0
      if (create_tmp_files_for_attachments(fp_body, file, e_new, b->parts, protected_headers) < 0)
380
0
      {
381
0
        return -1;
382
0
      }
383
0
    }
384
0
    else
385
0
    {
386
0
      buf_reset(file);
387
0
      if (b->filename)
388
0
      {
389
0
        buf_strcpy(file, b->filename);
390
0
        b->d_filename = mutt_str_dup(b->filename);
391
0
      }
392
0
      else
393
0
      {
394
        /* avoid Content-Disposition: header with temporary filename */
395
0
        b->use_disp = false;
396
0
      }
397
398
      /* set up state flags */
399
400
0
      state.flags = 0;
401
402
0
      if (b->type == TYPE_TEXT)
403
0
      {
404
0
        if (mutt_istr_equal("yes", mutt_param_get(&b->parameter, "x-mutt-noconv")))
405
0
        {
406
0
          b->noconv = true;
407
0
        }
408
0
        else
409
0
        {
410
0
          state.flags |= STATE_CHARCONV;
411
0
          b->noconv = false;
412
0
        }
413
414
0
        mutt_param_delete(&b->parameter, "x-mutt-noconv");
415
0
      }
416
417
0
      mutt_adv_mktemp(file);
418
0
      state.fp_out = mutt_file_fopen(buf_string(file), "w");
419
0
      if (!state.fp_out)
420
0
        return -1;
421
422
0
      SecurityFlags sec_type = SEC_NO_FLAGS;
423
0
      if (((WithCrypto & APPLICATION_PGP) != 0) && sec_type == SEC_NO_FLAGS)
424
0
        sec_type = mutt_is_application_pgp(b);
425
0
      if (((WithCrypto & APPLICATION_SMIME) != 0) && sec_type == SEC_NO_FLAGS)
426
0
        sec_type = mutt_is_application_smime(b);
427
0
      if (sec_type & (SEC_ENCRYPT | SEC_SIGN))
428
0
      {
429
0
        if (sec_type & SEC_ENCRYPT)
430
0
        {
431
0
          if (!crypt_valid_passphrase(sec_type))
432
0
            return -1;
433
0
          if (sec_type & APPLICATION_SMIME)
434
0
            crypt_smime_getkeys(e_new->env);
435
0
          mutt_message(_("Decrypting message..."));
436
0
        }
437
438
0
        if (mutt_body_handler(b, &state) < 0)
439
0
        {
440
0
          mutt_error(_("Decryption failed"));
441
0
          return -1;
442
0
        }
443
444
        /* Is this the first body part? Then save the headers. */
445
0
        if ((b == body) && !protected_headers)
446
0
        {
447
0
          protected_headers = b->mime_headers;
448
0
          b->mime_headers = NULL;
449
0
        }
450
451
0
        e_new->security |= sec_type;
452
0
        b->type = TYPE_TEXT;
453
0
        mutt_str_replace(&b->subtype, "plain");
454
0
        if (sec_type & APPLICATION_PGP)
455
0
          mutt_param_delete(&b->parameter, "x-action");
456
0
      }
457
0
      else
458
0
      {
459
0
        mutt_decode_attachment(b, &state);
460
0
      }
461
462
0
      if (mutt_file_fclose(&state.fp_out) != 0)
463
0
        return -1;
464
465
0
      mutt_str_replace(&b->filename, buf_string(file));
466
0
      b->unlink = true;
467
468
0
      mutt_stamp_attachment(b);
469
470
0
      mutt_body_free(&b->parts);
471
0
      if (b->email)
472
0
        b->email->body = NULL; /* avoid dangling pointer */
473
0
    }
474
0
  }
475
476
0
  return 0;
477
0
}
478
479
/**
480
 * mutt_prepare_template - Prepare a message template
481
 * @param fp      If not NULL, file containing the template
482
 * @param m       If fp is NULL, the Mailbox containing the header with the template
483
 * @param e_new   The template is read into this Header
484
 * @param e       Email to recall/resend
485
 * @param resend  Set if resending (as opposed to recalling a postponed msg)
486
 *                Resent messages enable header weeding, and also
487
 *                discard any existing Message-ID and Mail-Followup-To
488
 * @retval  0 Success
489
 * @retval -1 Error
490
 */
491
int mutt_prepare_template(FILE *fp, struct Mailbox *m, struct Email *e_new,
492
                          struct Email *e, bool resend)
493
0
{
494
0
  struct Message *msg = NULL;
495
0
  struct Body *b = NULL;
496
0
  FILE *fp_body = NULL;
497
0
  int rc = -1;
498
0
  struct Envelope *protected_headers = NULL;
499
0
  struct Buffer *file = NULL;
500
501
0
  if (!fp && !(msg = mx_msg_open(m, e)))
502
0
    return -1;
503
504
0
  if (!fp)
505
0
    fp = msg->fp;
506
507
0
  fp_body = fp;
508
509
  /* parse the message header and MIME structure */
510
511
0
  if (!mutt_file_seek(fp, e->offset, SEEK_SET))
512
0
  {
513
0
    return -1;
514
0
  }
515
0
  e_new->offset = e->offset;
516
  /* enable header weeding for resent messages */
517
0
  e_new->env = mutt_rfc822_read_header(fp, e_new, true, resend);
518
0
  e_new->body->length = e->body->length;
519
0
  mutt_parse_part(fp, e_new->body);
520
521
  /* If resending a message, don't keep message_id or mail_followup_to.
522
   * Otherwise, we are resuming a postponed message, and want to keep those
523
   * headers if they exist.  */
524
0
  if (resend)
525
0
  {
526
0
    FREE(&e_new->env->message_id);
527
0
    mutt_addrlist_clear(&e_new->env->mail_followup_to);
528
0
  }
529
530
0
  SecurityFlags sec_type = SEC_NO_FLAGS;
531
0
  if (((WithCrypto & APPLICATION_PGP) != 0) && sec_type == SEC_NO_FLAGS)
532
0
    sec_type = mutt_is_multipart_encrypted(e_new->body);
533
0
  if (((WithCrypto & APPLICATION_SMIME) != 0) && sec_type == SEC_NO_FLAGS)
534
0
    sec_type = mutt_is_application_smime(e_new->body);
535
0
  if (sec_type != SEC_NO_FLAGS)
536
0
  {
537
0
    e_new->security |= sec_type;
538
0
    if (!crypt_valid_passphrase(sec_type))
539
0
      goto bail;
540
541
0
    mutt_message(_("Decrypting message..."));
542
0
    int ret = -1;
543
0
    if (sec_type & APPLICATION_PGP)
544
0
      ret = crypt_pgp_decrypt_mime(fp, &fp_body, e_new->body, &b);
545
0
    else if (sec_type & APPLICATION_SMIME)
546
0
      ret = crypt_smime_decrypt_mime(fp, &fp_body, e_new->body, &b);
547
0
    if ((ret == -1) || !b)
548
0
    {
549
0
      mutt_error(_("Could not decrypt postponed message"));
550
0
      goto bail;
551
0
    }
552
553
    /* throw away the outer layer and keep only the (now decrypted) inner part
554
     * with its headers. */
555
0
    mutt_body_free(&e_new->body);
556
0
    e_new->body = b;
557
558
0
    if (b->mime_headers)
559
0
    {
560
0
      protected_headers = b->mime_headers;
561
0
      b->mime_headers = NULL;
562
0
    }
563
564
0
    mutt_clear_error();
565
0
  }
566
567
  /* remove a potential multipart/signed layer - useful when
568
   * resending messages */
569
0
  if ((WithCrypto != 0) && mutt_is_multipart_signed(e_new->body))
570
0
  {
571
0
    e_new->security |= SEC_SIGN;
572
0
    if (((WithCrypto & APPLICATION_PGP) != 0) &&
573
0
        mutt_istr_equal(mutt_param_get(&e_new->body->parameter, "protocol"), "application/pgp-signature"))
574
0
    {
575
0
      e_new->security |= APPLICATION_PGP;
576
0
    }
577
0
    else if (WithCrypto & APPLICATION_SMIME)
578
0
    {
579
0
      e_new->security |= APPLICATION_SMIME;
580
0
    }
581
582
    /* destroy the signature */
583
0
    mutt_body_free(&e_new->body->parts->next);
584
0
    e_new->body = mutt_remove_multipart(e_new->body);
585
586
0
    if (e_new->body->mime_headers)
587
0
    {
588
0
      mutt_env_free(&protected_headers);
589
0
      protected_headers = e_new->body->mime_headers;
590
0
      e_new->body->mime_headers = NULL;
591
0
    }
592
0
  }
593
594
  /* We don't need no primary multipart/mixed. */
595
0
  if ((e_new->body->type == TYPE_MULTIPART) && mutt_istr_equal(e_new->body->subtype, "mixed"))
596
0
    e_new->body = mutt_remove_multipart(e_new->body);
597
598
0
  file = buf_pool_get();
599
600
  /* create temporary files for all attachments */
601
0
  if (create_tmp_files_for_attachments(fp_body, file, e_new, e_new->body, protected_headers) < 0)
602
0
  {
603
0
    goto bail;
604
0
  }
605
606
0
  const bool c_crypt_protected_headers_read = cs_subset_bool(NeoMutt->sub, "crypt_protected_headers_read");
607
0
  if (c_crypt_protected_headers_read && protected_headers && protected_headers->subject &&
608
0
      !mutt_str_equal(e_new->env->subject, protected_headers->subject))
609
0
  {
610
0
    mutt_str_replace(&e_new->env->subject, protected_headers->subject);
611
0
  }
612
0
  mutt_env_free(&protected_headers);
613
614
  /* Fix encryption flags. */
615
616
  /* No inline if multipart. */
617
0
  if ((WithCrypto != 0) && (e_new->security & SEC_INLINE) && e_new->body->next)
618
0
    e_new->security &= ~SEC_INLINE;
619
620
  /* Do we even support multiple mechanisms? */
621
0
  e_new->security &= WithCrypto | ~(APPLICATION_PGP | APPLICATION_SMIME);
622
623
  /* Theoretically, both could be set. Take the one the user wants to set by default. */
624
0
  if ((e_new->security & APPLICATION_PGP) && (e_new->security & APPLICATION_SMIME))
625
0
  {
626
0
    const bool c_smime_is_default = cs_subset_bool(NeoMutt->sub, "smime_is_default");
627
0
    if (c_smime_is_default)
628
0
      e_new->security &= ~APPLICATION_PGP;
629
0
    else
630
0
      e_new->security &= ~APPLICATION_SMIME;
631
0
  }
632
633
0
  mutt_rfc3676_space_unstuff(e_new);
634
635
0
  rc = 0;
636
637
0
bail:
638
639
  /* that's it. */
640
0
  buf_pool_release(&file);
641
0
  if (fp_body != fp)
642
0
    mutt_file_fclose(&fp_body);
643
0
  if (msg)
644
0
    mx_msg_close(m, &msg);
645
646
0
  if (rc == -1)
647
0
  {
648
0
    mutt_env_free(&e_new->env);
649
0
    mutt_body_free(&e_new->body);
650
0
  }
651
652
0
  return rc;
653
0
}
654
655
/**
656
 * mutt_get_postponed - Recall a postponed message
657
 * @param[in]  m_cur   Current mailbox
658
 * @param[in]  hdr     envelope/attachment info for recalled message
659
 * @param[out] cur     if message was a reply, 'cur' is set to the message which 'hdr' is in reply to
660
 * @param[in]  fcc     fcc for the recalled message
661
 * @retval -1         Error/no messages
662
 * @retval 0          Normal exit
663
 * @retval #SEND_REPLY Recalled message is a reply
664
 */
665
int mutt_get_postponed(struct Mailbox *m_cur, struct Email *hdr,
666
                       struct Email **cur, struct Buffer *fcc)
667
0
{
668
0
  const char *const c_postponed = cs_subset_string(NeoMutt->sub, "postponed");
669
0
  if (!c_postponed)
670
0
    return -1;
671
672
0
  struct Email *e = NULL;
673
0
  int rc = SEND_POSTPONED;
674
0
  const char *p = NULL;
675
676
0
  struct Mailbox *m = mx_path_resolve(c_postponed);
677
0
  if (m_cur != m)
678
0
  {
679
0
    if (!mx_mbox_open(m, MUTT_NOSORT))
680
0
    {
681
0
      PostCount = 0;
682
0
      mutt_error(_("No postponed messages"));
683
0
      mailbox_free(&m);
684
0
      return -1;
685
0
    }
686
0
  }
687
688
0
  mx_mbox_check(m);
689
690
0
  if (m->msg_count == 0)
691
0
  {
692
0
    PostCount = 0;
693
0
    mutt_error(_("No postponed messages"));
694
0
    if (m_cur != m)
695
0
    {
696
0
      mx_fastclose_mailbox(m, false);
697
0
      mailbox_free(&m);
698
0
    }
699
0
    return -1;
700
0
  }
701
702
  /* avoid the "purge deleted messages" prompt */
703
0
  const enum QuadOption c_delete = cs_subset_quad(NeoMutt->sub, "delete");
704
0
  cs_subset_str_native_set(NeoMutt->sub, "delete", MUTT_YES, NULL);
705
706
0
  if (m->msg_count == 1)
707
0
  {
708
    /* only one message, so just use that one. */
709
0
    e = m->emails[0];
710
0
  }
711
0
  else if (!(e = dlg_select_postponed_email(m)))
712
0
  {
713
0
    rc = -1;
714
0
    goto cleanup;
715
0
  }
716
717
0
  if (mutt_prepare_template(NULL, m, hdr, e, false) < 0)
718
0
  {
719
0
    rc = -1;
720
0
    goto cleanup;
721
0
  }
722
723
  /* finished with this message, so delete it. */
724
0
  mutt_set_flag(m, e, MUTT_DELETE, true, true);
725
0
  mutt_set_flag(m, e, MUTT_PURGE, true, true);
726
727
  /* update the count for the status display */
728
0
  PostCount = m->msg_count - m->msg_deleted;
729
730
0
  struct ListNode *np = NULL, *tmp = NULL;
731
0
  STAILQ_FOREACH_SAFE(np, &hdr->env->userhdrs, entries, tmp)
732
0
  {
733
0
    size_t plen = 0;
734
    // Check for header names: most specific first
735
0
    if ((plen = mutt_istr_startswith(np->data, "X-Mutt-References:")) ||
736
0
        (plen = mutt_istr_startswith(np->data, "Mutt-References:")))
737
0
    {
738
      /* if a mailbox is currently open, look to see if the original message
739
       * the user attempted to reply to is in this mailbox */
740
0
      if (m_cur)
741
0
      {
742
0
        p = mutt_str_skip_email_wsp(np->data + plen);
743
0
        if (!m_cur->id_hash)
744
0
          m_cur->id_hash = mutt_make_id_hash(m_cur);
745
0
        *cur = mutt_hash_find(m_cur->id_hash, p);
746
747
0
        if (*cur)
748
0
          rc |= SEND_REPLY;
749
0
      }
750
0
    }
751
    // Check for header names: most specific first
752
0
    else if ((plen = mutt_istr_startswith(np->data, "X-Mutt-Fcc:")) ||
753
0
             (plen = mutt_istr_startswith(np->data, "Mutt-Fcc:")))
754
0
    {
755
0
      p = mutt_str_skip_email_wsp(np->data + plen);
756
0
      buf_strcpy(fcc, p);
757
0
      buf_pretty_mailbox(fcc);
758
759
      /* note that mutt-fcc was present.  we do this because we want to add a
760
       * default fcc if the header was missing, but preserve the request of the
761
       * user to not make a copy if the header field is present, but empty. */
762
0
      rc |= SEND_POSTPONED_FCC;
763
0
    }
764
    // Check for header names: most specific first
765
0
    else if (((WithCrypto & APPLICATION_PGP) != 0) &&
766
0
             ((plen = mutt_istr_startswith(np->data, "X-Mutt-PGP:")) ||
767
0
              (plen = mutt_istr_startswith(np->data, "Mutt-PGP:")) ||
768
0
              (plen = mutt_istr_startswith(np->data, "Pgp:"))))
769
0
    {
770
0
      hdr->security = mutt_parse_crypt_hdr(np->data + plen, true, APPLICATION_PGP);
771
0
      hdr->security |= APPLICATION_PGP;
772
0
    }
773
    // Check for header names: most specific first
774
0
    else if (((WithCrypto & APPLICATION_SMIME) != 0) &&
775
0
             ((plen = mutt_istr_startswith(np->data, "X-Mutt-SMIME:")) ||
776
0
              (plen = mutt_istr_startswith(np->data, "Mutt-SMIME:"))))
777
0
    {
778
0
      hdr->security = mutt_parse_crypt_hdr(np->data + plen, true, APPLICATION_SMIME);
779
0
      hdr->security |= APPLICATION_SMIME;
780
0
    }
781
#ifdef MIXMASTER
782
    // Check for header names: most specific first
783
    else if ((plen = mutt_istr_startswith(np->data, "X-Mutt-Mix:")) ||
784
             (plen = mutt_istr_startswith(np->data, "Mutt-Mix:")))
785
    {
786
      mutt_list_free(&hdr->chain);
787
788
      char *t = strtok(np->data + plen, " \t\n");
789
      while (t)
790
      {
791
        mutt_list_insert_tail(&hdr->chain, mutt_str_dup(t));
792
        t = strtok(NULL, " \t\n");
793
      }
794
    }
795
#endif
796
0
    else
797
0
    {
798
      // skip header removal
799
0
      continue;
800
0
    }
801
802
    // remove the header
803
0
    STAILQ_REMOVE(&hdr->env->userhdrs, np, ListNode, entries);
804
0
    FREE(&np->data);
805
0
    FREE(&np);
806
0
  }
807
808
0
  const bool c_crypt_opportunistic_encrypt = cs_subset_bool(NeoMutt->sub, "crypt_opportunistic_encrypt");
809
0
  if (c_crypt_opportunistic_encrypt)
810
0
    crypt_opportunistic_encrypt(hdr);
811
812
0
cleanup:
813
0
  if (m_cur != m)
814
0
  {
815
0
    hardclose(m);
816
0
    mailbox_free(&m);
817
0
  }
818
819
0
  cs_subset_str_native_set(NeoMutt->sub, "delete", c_delete, NULL);
820
0
  return rc;
821
0
}