Coverage Report

Created: 2025-03-11 06:49

/src/neomutt/copy.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Duplicate the structure of an entire email
4
 *
5
 * @authors
6
 * Copyright (C) 1996-2000,2002,2014 Michael R. Elkins <me@mutt.org>
7
 * Copyright (C) 2017-2024 Richard Russon <rich@flatcap.org>
8
 * Copyright (C) 2017-2023 Pietro Cerutti <gahr@gahr.ch>
9
 * Copyright (C) 2024 Dennis Schön <mail@dennis-schoen.de>
10
 *
11
 * @copyright
12
 * This program is free software: you can redistribute it and/or modify it under
13
 * the terms of the GNU General Public License as published by the Free Software
14
 * Foundation, either version 2 of the License, or (at your option) any later
15
 * version.
16
 *
17
 * This program is distributed in the hope that it will be useful, but WITHOUT
18
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
20
 * details.
21
 *
22
 * You should have received a copy of the GNU General Public License along with
23
 * this program.  If not, see <http://www.gnu.org/licenses/>.
24
 */
25
26
/**
27
 * @page neo_copy Duplicate the structure of an entire email
28
 *
29
 * Duplicate the structure of an entire email
30
 */
31
32
#include "config.h"
33
#include <ctype.h>
34
#include <inttypes.h> // IWYU pragma: keep
35
#include <locale.h>
36
#include <stdbool.h>
37
#include <string.h>
38
#include "mutt/lib.h"
39
#include "address/lib.h"
40
#include "config/lib.h"
41
#include "email/lib.h"
42
#include "core/lib.h"
43
#include "gui/lib.h"
44
#include "mutt.h"
45
#include "copy.h"
46
#include "expando/lib.h"
47
#include "index/lib.h"
48
#include "ncrypt/lib.h"
49
#include "pager/lib.h"
50
#include "send/lib.h"
51
#include "globals.h"
52
#include "handler.h"
53
#include "mx.h"
54
#ifdef USE_NOTMUCH
55
#include "notmuch/lib.h"
56
#include "muttlib.h"
57
#endif
58
#ifdef ENABLE_NLS
59
#include <libintl.h>
60
#endif
61
62
static int address_header_decode(char **h);
63
static int copy_delete_attach(struct Body *b, FILE *fp_in, FILE *fp_out,
64
                              const char *quoted_date);
65
66
ARRAY_HEAD(HeaderArray, char *);
67
68
/**
69
 * add_one_header - Add a header to a Headers array
70
 * @param headers Headers array
71
 * @param pos     Position to insert new header
72
 * @param value   Text to insert
73
 *
74
 * If a header already exists in that position, the new text will be
75
 * concatenated on the old.
76
 */
77
static void add_one_header(struct HeaderArray *headers, int pos, char *value)
78
0
{
79
0
  char **old = ARRAY_GET(headers, pos);
80
0
  if (old && *old)
81
0
  {
82
0
    char *new_value = NULL;
83
0
    mutt_str_asprintf(&new_value, "%s%s", *old, value);
84
0
    FREE(old);
85
0
    FREE(&value);
86
0
    value = new_value;
87
0
  }
88
0
  ARRAY_SET(headers, pos, value);
89
0
}
90
91
/**
92
 * mutt_copy_hdr - Copy header from one file to another
93
 * @param fp_in     FILE pointer to read from
94
 * @param fp_out    FILE pointer to write to
95
 * @param off_start Offset to start from
96
 * @param off_end   Offset to finish at
97
 * @param chflags   Flags, see #CopyHeaderFlags
98
 * @param prefix    Prefix for quoting headers
99
 * @param wraplen   Width to wrap at (when chflags & CH_DISPLAY)
100
 * @retval  0 Success
101
 * @retval -1 Failure
102
 *
103
 * Ok, the only reason for not merging this with mutt_copy_header() below is to
104
 * avoid creating a Email structure in message_handler().  Also, this one will
105
 * wrap headers much more aggressively than the other one.
106
 */
107
int mutt_copy_hdr(FILE *fp_in, FILE *fp_out, LOFF_T off_start, LOFF_T off_end,
108
                  CopyHeaderFlags chflags, const char *prefix, int wraplen)
109
0
{
110
0
  bool from = false;
111
0
  bool this_is_from = false;
112
0
  bool ignore = false;
113
0
  char buf[1024] = { 0 }; /* should be long enough to get most fields in one pass */
114
0
  char *nl = NULL;
115
0
  struct HeaderArray headers = ARRAY_HEAD_INITIALIZER;
116
0
  int hdr_count;
117
0
  int x;
118
0
  char *this_one = NULL;
119
0
  size_t this_one_len = 0;
120
121
0
  if (off_start < 0)
122
0
    return -1;
123
124
0
  if (ftello(fp_in) != off_start)
125
0
    if (!mutt_file_seek(fp_in, off_start, SEEK_SET))
126
0
      return -1;
127
128
0
  buf[0] = '\n';
129
0
  buf[1] = '\0';
130
131
0
  if ((chflags & (CH_REORDER | CH_WEED | CH_MIME | CH_DECODE | CH_PREFIX | CH_WEED_DELIVERED)) == 0)
132
0
  {
133
    /* Without these flags to complicate things
134
     * we can do a more efficient line to line copying */
135
0
    while (ftello(fp_in) < off_end)
136
0
    {
137
0
      nl = strchr(buf, '\n');
138
139
0
      if (!fgets(buf, sizeof(buf), fp_in))
140
0
        break;
141
142
      /* Is it the beginning of a header? */
143
0
      if (nl && (buf[0] != ' ') && (buf[0] != '\t'))
144
0
      {
145
0
        ignore = true;
146
0
        if (!from && mutt_str_startswith(buf, "From "))
147
0
        {
148
0
          if ((chflags & CH_FROM) == 0)
149
0
            continue;
150
0
          from = true;
151
0
        }
152
0
        else if ((chflags & CH_NOQFROM) && mutt_istr_startswith(buf, ">From "))
153
0
        {
154
0
          continue;
155
0
        }
156
0
        else if ((buf[0] == '\n') || ((buf[0] == '\r') && (buf[1] == '\n')))
157
0
        {
158
0
          break; /* end of header */
159
0
        }
160
161
0
        if ((chflags & (CH_UPDATE | CH_XMIT | CH_NOSTATUS)) &&
162
0
            (mutt_istr_startswith(buf, "Status:") || mutt_istr_startswith(buf, "X-Status:")))
163
0
        {
164
0
          continue;
165
0
        }
166
0
        if ((chflags & (CH_UPDATE_LEN | CH_XMIT | CH_NOLEN)) &&
167
0
            (mutt_istr_startswith(buf, "Content-Length:") ||
168
0
             mutt_istr_startswith(buf, "Lines:")))
169
0
        {
170
0
          continue;
171
0
        }
172
0
        if ((chflags & CH_UPDATE_REFS) && mutt_istr_startswith(buf, "References:"))
173
0
        {
174
0
          continue;
175
0
        }
176
0
        if ((chflags & CH_UPDATE_IRT) && mutt_istr_startswith(buf, "In-Reply-To:"))
177
0
        {
178
0
          continue;
179
0
        }
180
0
        if (chflags & CH_UPDATE_LABEL && mutt_istr_startswith(buf, "X-Label:"))
181
0
          continue;
182
0
        if ((chflags & CH_UPDATE_SUBJECT) && mutt_istr_startswith(buf, "Subject:"))
183
0
        {
184
0
          continue;
185
0
        }
186
187
0
        ignore = false;
188
0
      }
189
190
0
      if (!ignore && (fputs(buf, fp_out) == EOF))
191
0
        return -1;
192
0
    }
193
0
    return 0;
194
0
  }
195
196
0
  hdr_count = 1;
197
0
  x = 0;
198
199
  /* We are going to read and collect the headers in an array
200
   * so we are able to do re-ordering.
201
   * First count the number of entries in the array */
202
0
  if (chflags & CH_REORDER)
203
0
  {
204
0
    struct ListNode *np = NULL;
205
0
    STAILQ_FOREACH(np, &HeaderOrderList, entries)
206
0
    {
207
0
      mutt_debug(LL_DEBUG3, "Reorder list: %s\n", np->data);
208
0
      hdr_count++;
209
0
    }
210
0
  }
211
212
0
  mutt_debug(LL_DEBUG1, "WEED is %sset\n", (chflags & CH_WEED) ? "" : "not ");
213
214
0
  ARRAY_RESERVE(&headers, hdr_count);
215
216
  /* Read all the headers into the array */
217
0
  while (ftello(fp_in) < off_end)
218
0
  {
219
0
    nl = strchr(buf, '\n');
220
221
    /* Read a line */
222
0
    if (!fgets(buf, sizeof(buf), fp_in))
223
0
      break;
224
225
    /* Is it the beginning of a header? */
226
0
    if (nl && (buf[0] != ' ') && (buf[0] != '\t'))
227
0
    {
228
      /* Do we have anything pending? */
229
0
      if (this_one)
230
0
      {
231
0
        if (chflags & CH_DECODE)
232
0
        {
233
0
          if (address_header_decode(&this_one) == 0)
234
0
            rfc2047_decode(&this_one);
235
0
          this_one_len = mutt_str_len(this_one);
236
237
          /* Convert CRLF line endings to LF */
238
0
          if ((this_one_len > 2) && (this_one[this_one_len - 2] == '\r') &&
239
0
              (this_one[this_one_len - 1] == '\n'))
240
0
          {
241
0
            this_one[this_one_len - 2] = '\n';
242
0
            this_one[this_one_len - 1] = '\0';
243
0
          }
244
0
        }
245
246
0
        add_one_header(&headers, x, this_one);
247
0
        this_one = NULL;
248
0
      }
249
250
0
      ignore = true;
251
0
      this_is_from = false;
252
0
      if (!from && mutt_str_startswith(buf, "From "))
253
0
      {
254
0
        if ((chflags & CH_FROM) == 0)
255
0
          continue;
256
0
        this_is_from = true;
257
0
        from = true;
258
0
      }
259
0
      else if ((buf[0] == '\n') || ((buf[0] == '\r') && (buf[1] == '\n')))
260
0
      {
261
0
        break; /* end of header */
262
0
      }
263
264
      /* note: CH_FROM takes precedence over header weeding. */
265
0
      if (!((chflags & CH_FROM) && (chflags & CH_FORCE_FROM) && this_is_from) &&
266
0
          (chflags & CH_WEED) && mutt_matches_ignore(buf))
267
0
      {
268
0
        continue;
269
0
      }
270
0
      if ((chflags & CH_WEED_DELIVERED) && mutt_istr_startswith(buf, "Delivered-To:"))
271
0
      {
272
0
        continue;
273
0
      }
274
0
      if ((chflags & (CH_UPDATE | CH_XMIT | CH_NOSTATUS)) &&
275
0
          (mutt_istr_startswith(buf, "Status:") || mutt_istr_startswith(buf, "X-Status:")))
276
0
      {
277
0
        continue;
278
0
      }
279
0
      if ((chflags & (CH_UPDATE_LEN | CH_XMIT | CH_NOLEN)) &&
280
0
          (mutt_istr_startswith(buf, "Content-Length:") || mutt_istr_startswith(buf, "Lines:")))
281
0
      {
282
0
        continue;
283
0
      }
284
0
      if ((chflags & CH_MIME))
285
0
      {
286
0
        if (mutt_istr_startswith(buf, "mime-version:"))
287
0
        {
288
0
          continue;
289
0
        }
290
0
        size_t plen = mutt_istr_startswith(buf, "content-");
291
0
        if ((plen != 0) && (mutt_istr_startswith(buf + plen, "transfer-encoding:") ||
292
0
                            mutt_istr_startswith(buf + plen, "type:")))
293
0
        {
294
0
          continue;
295
0
        }
296
0
      }
297
0
      if ((chflags & CH_UPDATE_REFS) && mutt_istr_startswith(buf, "References:"))
298
0
      {
299
0
        continue;
300
0
      }
301
0
      if ((chflags & CH_UPDATE_IRT) && mutt_istr_startswith(buf, "In-Reply-To:"))
302
0
      {
303
0
        continue;
304
0
      }
305
0
      if ((chflags & CH_UPDATE_LABEL) && mutt_istr_startswith(buf, "X-Label:"))
306
0
        continue;
307
0
      if ((chflags & CH_UPDATE_SUBJECT) && mutt_istr_startswith(buf, "Subject:"))
308
0
      {
309
0
        continue;
310
0
      }
311
312
      /* Find x -- the array entry where this header is to be saved */
313
0
      if (chflags & CH_REORDER)
314
0
      {
315
0
        struct ListNode *np = NULL;
316
0
        x = 0;
317
0
        int match = -1;
318
0
        size_t match_len = 0;
319
320
0
        STAILQ_FOREACH(np, &HeaderOrderList, entries)
321
0
        {
322
0
          size_t hdr_order_len = mutt_str_len(np->data);
323
0
          if (mutt_istrn_equal(buf, np->data, hdr_order_len))
324
0
          {
325
0
            if ((match == -1) || (hdr_order_len > match_len))
326
0
            {
327
0
              match = x;
328
0
              match_len = hdr_order_len;
329
0
            }
330
0
            mutt_debug(LL_DEBUG2, "Reorder: %s matches %s\n", np->data, buf);
331
0
          }
332
0
          x++;
333
0
        }
334
0
        if (match != -1)
335
0
          x = match;
336
0
      }
337
338
0
      ignore = false;
339
0
    } /* If beginning of header */
340
341
0
    if (!ignore)
342
0
    {
343
0
      mutt_debug(LL_DEBUG2, "Reorder: x = %d; hdr_count = %d\n", x, hdr_count);
344
0
      if (this_one)
345
0
      {
346
0
        size_t blen = mutt_str_len(buf);
347
348
0
        MUTT_MEM_REALLOC(&this_one, this_one_len + blen + 1, char);
349
0
        mutt_strn_copy(this_one + this_one_len, buf, blen, blen + 1);
350
0
        this_one_len += blen;
351
0
      }
352
0
      else
353
0
      {
354
0
        this_one = mutt_str_dup(buf);
355
0
        this_one_len = mutt_str_len(this_one);
356
0
      }
357
0
    }
358
0
  } /* while (ftello (fp_in) < off_end) */
359
360
  /* Do we have anything pending?  -- XXX, same code as in above in the loop. */
361
0
  if (this_one)
362
0
  {
363
0
    if (chflags & CH_DECODE)
364
0
    {
365
0
      if (address_header_decode(&this_one) == 0)
366
0
        rfc2047_decode(&this_one);
367
0
      this_one_len = mutt_str_len(this_one);
368
0
    }
369
370
0
    add_one_header(&headers, x, this_one);
371
0
    this_one = NULL;
372
0
  }
373
374
  /* Now output the headers in order */
375
0
  bool error = false;
376
0
  char **hp = NULL;
377
0
  const short c_wrap = cs_subset_number(NeoMutt->sub, "wrap");
378
379
0
  ARRAY_FOREACH(hp, &headers)
380
0
  {
381
0
    if (!error && hp && *hp)
382
0
    {
383
      /* We couldn't do the prefixing when reading because RFC2047
384
       * decoding may have concatenated lines.  */
385
0
      if (chflags & (CH_DECODE | CH_PREFIX))
386
0
      {
387
0
        const char *pre = (chflags & CH_PREFIX) ? prefix : NULL;
388
0
        wraplen = mutt_window_wrap_cols(wraplen, c_wrap);
389
390
0
        if (mutt_write_one_header(fp_out, 0, *hp, pre, wraplen, chflags, NeoMutt->sub) == -1)
391
0
        {
392
0
          error = true;
393
0
        }
394
0
      }
395
0
      else
396
0
      {
397
0
        if (fputs(*hp, fp_out) == EOF)
398
0
        {
399
0
          error = true;
400
0
        }
401
0
      }
402
0
    }
403
404
0
    FREE(hp);
405
0
  }
406
0
  ARRAY_FREE(&headers);
407
408
0
  if (error)
409
0
    return -1;
410
0
  return 0;
411
0
}
412
413
/**
414
 * mutt_copy_header - Copy Email header
415
 * @param fp_in    FILE pointer to read from
416
 * @param e        Email
417
 * @param fp_out   FILE pointer to write to
418
 * @param chflags  See #CopyHeaderFlags
419
 * @param prefix   Prefix for quoting headers (if #CH_PREFIX is set)
420
 * @param wraplen  Width to wrap at (when chflags & CH_DISPLAY)
421
 * @retval  0 Success
422
 * @retval -1 Failure
423
 */
424
int mutt_copy_header(FILE *fp_in, struct Email *e, FILE *fp_out,
425
                     CopyHeaderFlags chflags, const char *prefix, int wraplen)
426
0
{
427
0
  char *temp_hdr = NULL;
428
0
  const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
429
430
0
  if (e->env)
431
0
  {
432
0
    chflags |= ((e->env->changed & MUTT_ENV_CHANGED_IRT) ? CH_UPDATE_IRT : 0) |
433
0
               ((e->env->changed & MUTT_ENV_CHANGED_REFS) ? CH_UPDATE_REFS : 0) |
434
0
               ((e->env->changed & MUTT_ENV_CHANGED_XLABEL) ? CH_UPDATE_LABEL : 0) |
435
0
               ((e->env->changed & MUTT_ENV_CHANGED_SUBJECT) ? CH_UPDATE_SUBJECT : 0);
436
0
  }
437
438
0
  if (mutt_copy_hdr(fp_in, fp_out, e->offset, e->body->offset, chflags, prefix, wraplen) == -1)
439
0
    return -1;
440
441
0
  if (chflags & CH_TXTPLAIN)
442
0
  {
443
0
    char chsbuf[128] = { 0 };
444
0
    char buf[128] = { 0 };
445
0
    fputs("MIME-Version: 1.0\n", fp_out);
446
0
    fputs("Content-Transfer-Encoding: 8bit\n", fp_out);
447
0
    fputs("Content-Type: text/plain; charset=", fp_out);
448
0
    const char *const c_charset = cc_charset();
449
0
    mutt_ch_canonical_charset(chsbuf, sizeof(chsbuf), c_charset ? c_charset : "us-ascii");
450
0
    mutt_addr_cat(buf, sizeof(buf), chsbuf, MimeSpecials);
451
0
    fputs(buf, fp_out);
452
0
    fputc('\n', fp_out);
453
0
  }
454
455
0
  if ((chflags & CH_UPDATE_IRT) && !STAILQ_EMPTY(&e->env->in_reply_to) &&
456
0
      !(c_weed && mutt_matches_ignore("In-Reply-To")))
457
0
  {
458
0
    fputs("In-Reply-To:", fp_out);
459
0
    struct ListNode *np = NULL;
460
0
    STAILQ_FOREACH(np, &e->env->in_reply_to, entries)
461
0
    {
462
0
      fputc(' ', fp_out);
463
0
      fputs(np->data, fp_out);
464
0
    }
465
0
    fputc('\n', fp_out);
466
0
  }
467
468
0
  if ((chflags & CH_UPDATE_REFS) && !STAILQ_EMPTY(&e->env->references) &&
469
0
      !(c_weed && mutt_matches_ignore("References")))
470
0
  {
471
0
    fputs("References:", fp_out);
472
0
    mutt_write_references(&e->env->references, fp_out, 0);
473
0
    fputc('\n', fp_out);
474
0
  }
475
476
0
  if ((chflags & CH_UPDATE) && ((chflags & CH_NOSTATUS) == 0))
477
0
  {
478
0
    if ((e->old || e->read) && !(c_weed && mutt_matches_ignore("Status")))
479
0
    {
480
0
      fputs("Status: ", fp_out);
481
0
      if (e->read)
482
0
        fputs("RO", fp_out);
483
0
      else if (e->old)
484
0
        fputc('O', fp_out);
485
0
      fputc('\n', fp_out);
486
0
    }
487
488
0
    if ((e->flagged || e->replied) && !(c_weed && mutt_matches_ignore("X-Status")))
489
0
    {
490
0
      fputs("X-Status: ", fp_out);
491
0
      if (e->replied)
492
0
        fputc('A', fp_out);
493
0
      if (e->flagged)
494
0
        fputc('F', fp_out);
495
0
      fputc('\n', fp_out);
496
0
    }
497
0
  }
498
499
0
  if (chflags & CH_UPDATE_LEN && ((chflags & CH_NOLEN) == 0) &&
500
0
      !(c_weed && mutt_matches_ignore("Content-Length")))
501
0
  {
502
0
    fprintf(fp_out, "Content-Length: " OFF_T_FMT "\n", e->body->length);
503
0
    if ((e->lines != 0) || (e->body->length == 0))
504
0
      fprintf(fp_out, "Lines: %d\n", e->lines);
505
0
  }
506
507
#ifdef USE_NOTMUCH
508
  if (chflags & CH_VIRTUAL)
509
  {
510
    /* Add some fake headers based on notmuch data */
511
    char *folder = nm_email_get_folder(e);
512
    if (folder && !(c_weed && mutt_matches_ignore("Folder")))
513
    {
514
      char buf[1024] = { 0 };
515
      mutt_str_copy(buf, folder, sizeof(buf));
516
      mutt_pretty_mailbox(buf, sizeof(buf));
517
518
      fputs("Folder: ", fp_out);
519
      fputs(buf, fp_out);
520
      fputc('\n', fp_out);
521
    }
522
  }
523
#endif
524
525
0
  struct Buffer *tags = buf_pool_get();
526
0
  driver_tags_get(&e->tags, tags);
527
0
  if (!buf_is_empty(tags) && !(c_weed && mutt_matches_ignore("Tags")))
528
0
  {
529
0
    fputs("Tags: ", fp_out);
530
0
    fputs(buf_string(tags), fp_out);
531
0
    fputc('\n', fp_out);
532
0
  }
533
0
  buf_pool_release(&tags);
534
535
0
  const struct Slist *const c_send_charset = cs_subset_slist(NeoMutt->sub, "send_charset");
536
0
  const short c_wrap = cs_subset_number(NeoMutt->sub, "wrap");
537
0
  if ((chflags & CH_UPDATE_LABEL) && e->env->x_label &&
538
0
      !(c_weed && mutt_matches_ignore("X-Label")))
539
0
  {
540
0
    temp_hdr = e->env->x_label;
541
    /* env->x_label isn't currently stored with direct references elsewhere.
542
     * Mailbox->label_hash strdups the keys.  But to be safe, encode a copy */
543
0
    if (!(chflags & CH_DECODE))
544
0
    {
545
0
      temp_hdr = mutt_str_dup(temp_hdr);
546
0
      rfc2047_encode(&temp_hdr, NULL, sizeof("X-Label:"), c_send_charset);
547
0
    }
548
0
    if (mutt_write_one_header(fp_out, "X-Label", temp_hdr, (chflags & CH_PREFIX) ? prefix : 0,
549
0
                              mutt_window_wrap_cols(wraplen, c_wrap), chflags,
550
0
                              NeoMutt->sub) == -1)
551
0
    {
552
0
      return -1;
553
0
    }
554
0
    if (!(chflags & CH_DECODE))
555
0
      FREE(&temp_hdr);
556
0
  }
557
558
0
  if ((chflags & CH_UPDATE_SUBJECT) && e->env->subject &&
559
0
      !(c_weed && mutt_matches_ignore("Subject")))
560
0
  {
561
0
    temp_hdr = e->env->subject;
562
    /* env->subject is directly referenced in Mailbox->subj_hash, so we
563
     * have to be careful not to encode (and thus free) that memory. */
564
0
    if (!(chflags & CH_DECODE))
565
0
    {
566
0
      temp_hdr = mutt_str_dup(temp_hdr);
567
0
      rfc2047_encode(&temp_hdr, NULL, sizeof("Subject:"), c_send_charset);
568
0
    }
569
0
    if (mutt_write_one_header(fp_out, "Subject", temp_hdr, (chflags & CH_PREFIX) ? prefix : 0,
570
0
                              mutt_window_wrap_cols(wraplen, c_wrap), chflags,
571
0
                              NeoMutt->sub) == -1)
572
0
    {
573
0
      return -1;
574
0
    }
575
0
    if (!(chflags & CH_DECODE))
576
0
      FREE(&temp_hdr);
577
0
  }
578
579
0
  if ((chflags & CH_NONEWLINE) == 0)
580
0
  {
581
0
    if (chflags & CH_PREFIX)
582
0
      fputs(prefix, fp_out);
583
0
    fputc('\n', fp_out); /* add header terminator */
584
0
  }
585
586
0
  if (ferror(fp_out) || feof(fp_out))
587
0
    return -1;
588
589
0
  return 0;
590
0
}
591
592
/**
593
 * count_delete_lines - Count lines to be deleted in this email body
594
 * @param fp      FILE pointer to read from
595
 * @param b       Email Body
596
 * @param length  Number of bytes to be deleted
597
 * @param datelen Length of the date
598
 * @retval num Number of lines to be deleted
599
 * @retval -1 on error
600
 *
601
 * Count the number of lines and bytes to be deleted in this body
602
 */
603
static int count_delete_lines(FILE *fp, struct Body *b, LOFF_T *length, size_t datelen)
604
0
{
605
0
  int dellines = 0;
606
607
0
  if (b->deleted)
608
0
  {
609
0
    if (!mutt_file_seek(fp, b->offset, SEEK_SET))
610
0
    {
611
0
      return -1;
612
0
    }
613
0
    for (long l = b->length; l; l--)
614
0
    {
615
0
      const int ch = getc(fp);
616
0
      if (ch == EOF)
617
0
        break;
618
0
      if (ch == '\n')
619
0
        dellines++;
620
0
    }
621
    /* 3 and 89 come from the added header of three lines in
622
     * copy_delete_attach().  89 is the size of the header(including
623
     * the newlines, tabs, and a single digit length), not including
624
     * the date length. */
625
0
    dellines -= 3;
626
0
    *length -= b->length - (89 + datelen);
627
    /* Count the number of digits exceeding the first one to write the size */
628
0
    for (long l = 10; b->length >= l; l *= 10)
629
0
      (*length)++;
630
0
  }
631
0
  else
632
0
  {
633
0
    for (b = b->parts; b; b = b->next)
634
0
    {
635
0
      const int del = count_delete_lines(fp, b, length, datelen);
636
0
      if (del == -1)
637
0
      {
638
0
        return -1;
639
0
      }
640
0
      dellines += del;
641
0
    }
642
0
  }
643
0
  return dellines;
644
0
}
645
646
/**
647
 * mutt_copy_message_fp - Make a copy of a message from a FILE pointer
648
 * @param fp_out  Where to write output
649
 * @param fp_in   Where to get input
650
 * @param e       Email being copied
651
 * @param cmflags Flags, see #CopyMessageFlags
652
 * @param chflags Flags, see #CopyHeaderFlags
653
 * @param wraplen Width to wrap at (when chflags & CH_DISPLAY)
654
 * @retval  0 Success
655
 * @retval -1 Failure
656
 */
657
int mutt_copy_message_fp(FILE *fp_out, FILE *fp_in, struct Email *e,
658
                         CopyMessageFlags cmflags, CopyHeaderFlags chflags, int wraplen)
659
0
{
660
0
  struct Body *body = e->body;
661
0
  struct Buffer *prefix = buf_pool_get();
662
0
  LOFF_T new_offset = -1;
663
0
  int rc = 0;
664
665
0
  if (cmflags & MUTT_CM_PREFIX)
666
0
  {
667
0
    const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
668
0
    if (c_text_flowed)
669
0
    {
670
0
      buf_strcpy(prefix, ">");
671
0
    }
672
0
    else
673
0
    {
674
0
      const char *const c_attribution_locale = cs_subset_string(NeoMutt->sub, "attribution_locale");
675
0
      const struct Expando *c_indent_string = cs_subset_expando(NeoMutt->sub, "indent_string");
676
0
      struct Mailbox *m_cur = get_current_mailbox();
677
0
      setlocale(LC_TIME, NONULL(c_attribution_locale));
678
0
      mutt_make_string(prefix, -1, c_indent_string, m_cur, -1, e, MUTT_FORMAT_NO_FLAGS, NULL);
679
0
      setlocale(LC_TIME, "");
680
0
    }
681
0
  }
682
683
0
  if ((cmflags & MUTT_CM_NOHEADER) == 0)
684
0
  {
685
0
    if (cmflags & MUTT_CM_PREFIX)
686
0
    {
687
0
      chflags |= CH_PREFIX;
688
0
    }
689
0
    else if (e->attach_del && (chflags & CH_UPDATE_LEN))
690
0
    {
691
0
      int new_lines;
692
0
      int rc_attach_del = -1;
693
0
      LOFF_T new_length = body->length;
694
0
      struct Buffer *quoted_date = NULL;
695
696
0
      quoted_date = buf_pool_get();
697
0
      buf_addch(quoted_date, '"');
698
0
      mutt_date_make_date(quoted_date, cs_subset_bool(NeoMutt->sub, "local_date_header"));
699
0
      buf_addch(quoted_date, '"');
700
701
      /* Count the number of lines and bytes to be deleted */
702
0
      if (!mutt_file_seek(fp_in, body->offset, SEEK_SET))
703
0
      {
704
0
        goto attach_del_cleanup;
705
0
      }
706
0
      const int del = count_delete_lines(fp_in, body, &new_length, buf_len(quoted_date));
707
0
      if (del == -1)
708
0
      {
709
0
        goto attach_del_cleanup;
710
0
      }
711
0
      new_lines = e->lines - del;
712
713
      /* Copy the headers */
714
0
      if (mutt_copy_header(fp_in, e, fp_out, chflags | CH_NOLEN | CH_NONEWLINE, NULL, wraplen))
715
0
        goto attach_del_cleanup;
716
0
      fprintf(fp_out, "Content-Length: " OFF_T_FMT "\n", new_length);
717
0
      if (new_lines <= 0)
718
0
        new_lines = 0;
719
0
      else
720
0
        fprintf(fp_out, "Lines: %d\n", new_lines);
721
722
0
      putc('\n', fp_out);
723
0
      if (ferror(fp_out) || feof(fp_out))
724
0
        goto attach_del_cleanup;
725
0
      new_offset = ftello(fp_out);
726
727
      /* Copy the body */
728
0
      if (!mutt_file_seek(fp_in, body->offset, SEEK_SET))
729
0
        goto attach_del_cleanup;
730
0
      if (copy_delete_attach(body, fp_in, fp_out, buf_string(quoted_date)))
731
0
        goto attach_del_cleanup;
732
733
0
      buf_pool_release(&quoted_date);
734
735
0
      LOFF_T fail = ((ftello(fp_out) - new_offset) - new_length);
736
0
      if (fail)
737
0
      {
738
0
        mutt_error(ngettext("The length calculation was wrong by %ld byte",
739
0
                            "The length calculation was wrong by %ld bytes", fail),
740
0
                   (long) fail);
741
0
        new_length += fail;
742
0
      }
743
744
      /* Update original message if we are sync'ing a mailfolder */
745
0
      if (cmflags & MUTT_CM_UPDATE)
746
0
      {
747
0
        e->attach_del = false;
748
0
        e->lines = new_lines;
749
0
        body->offset = new_offset;
750
751
0
        body->length = new_length;
752
0
        mutt_body_free(&body->parts);
753
0
      }
754
755
0
      rc_attach_del = 0;
756
757
0
    attach_del_cleanup:
758
0
      buf_pool_release(&quoted_date);
759
0
      rc = rc_attach_del;
760
0
      goto done;
761
0
    }
762
763
0
    if (mutt_copy_header(fp_in, e, fp_out, chflags,
764
0
                         (chflags & CH_PREFIX) ? buf_string(prefix) : NULL, wraplen) == -1)
765
0
    {
766
0
      rc = -1;
767
0
      goto done;
768
0
    }
769
770
0
    new_offset = ftello(fp_out);
771
0
  }
772
773
0
  if (cmflags & MUTT_CM_DECODE)
774
0
  {
775
    /* now make a text/plain version of the message */
776
0
    struct State state = { 0 };
777
0
    state.fp_in = fp_in;
778
0
    state.fp_out = fp_out;
779
0
    if (cmflags & MUTT_CM_PREFIX)
780
0
      state.prefix = buf_string(prefix);
781
0
    if (cmflags & MUTT_CM_DISPLAY)
782
0
    {
783
0
      state.flags |= STATE_DISPLAY;
784
0
      state.wraplen = wraplen;
785
0
      const char *const c_pager = pager_get_pager(NeoMutt->sub);
786
0
      if (!c_pager)
787
0
        state.flags |= STATE_PAGER;
788
0
    }
789
0
    if (cmflags & MUTT_CM_PRINTING)
790
0
      state.flags |= STATE_PRINTING;
791
0
    if (cmflags & MUTT_CM_WEED)
792
0
      state.flags |= STATE_WEED;
793
0
    if (cmflags & MUTT_CM_CHARCONV)
794
0
      state.flags |= STATE_CHARCONV;
795
0
    if (cmflags & MUTT_CM_REPLYING)
796
0
      state.flags |= STATE_REPLYING;
797
798
0
    if ((WithCrypto != 0) && cmflags & MUTT_CM_VERIFY)
799
0
      state.flags |= STATE_VERIFY;
800
801
0
    rc = mutt_body_handler(body, &state);
802
0
  }
803
0
  else if ((WithCrypto != 0) && (cmflags & MUTT_CM_DECODE_CRYPT) && (e->security & SEC_ENCRYPT))
804
0
  {
805
0
    struct Body *cur = NULL;
806
0
    FILE *fp = NULL;
807
808
0
    if (((WithCrypto & APPLICATION_PGP) != 0) && (cmflags & MUTT_CM_DECODE_PGP) &&
809
0
        (e->security & APPLICATION_PGP) && (e->body->type == TYPE_MULTIPART))
810
0
    {
811
0
      if (crypt_pgp_decrypt_mime(fp_in, &fp, e->body, &cur))
812
0
      {
813
0
        rc = 1;
814
0
        goto done;
815
0
      }
816
0
      fputs("MIME-Version: 1.0\n", fp_out);
817
0
    }
818
819
0
    if (((WithCrypto & APPLICATION_SMIME) != 0) && (cmflags & MUTT_CM_DECODE_SMIME) &&
820
0
        (e->security & APPLICATION_SMIME) && (e->body->type == TYPE_APPLICATION))
821
0
    {
822
0
      if (crypt_smime_decrypt_mime(fp_in, &fp, e->body, &cur))
823
0
      {
824
0
        rc = 1;
825
0
        goto done;
826
0
      }
827
0
    }
828
829
0
    if (!cur)
830
0
    {
831
0
      mutt_error(_("No decryption engine available for message"));
832
0
      rc = 1;
833
0
      goto done;
834
0
    }
835
836
0
    mutt_write_mime_header(cur, fp_out, NeoMutt->sub);
837
0
    fputc('\n', fp_out);
838
839
0
    if (!mutt_file_seek(fp, cur->offset, SEEK_SET))
840
0
    {
841
0
      rc = 1;
842
0
      goto done;
843
0
    }
844
845
0
    if (mutt_file_copy_bytes(fp, fp_out, cur->length) == -1)
846
0
    {
847
0
      mutt_file_fclose(&fp);
848
0
      mutt_body_free(&cur);
849
0
      rc = 1;
850
0
      goto done;
851
0
    }
852
0
    mutt_body_free(&cur);
853
0
    mutt_file_fclose(&fp);
854
0
  }
855
0
  else
856
0
  {
857
0
    if (!mutt_file_seek(fp_in, body->offset, SEEK_SET))
858
0
    {
859
0
      rc = 1;
860
0
      goto done;
861
0
    }
862
0
    if (cmflags & MUTT_CM_PREFIX)
863
0
    {
864
0
      int c;
865
0
      size_t bytes = body->length;
866
867
0
      fputs(buf_string(prefix), fp_out);
868
869
0
      while (((c = fgetc(fp_in)) != EOF) && bytes--)
870
0
      {
871
0
        fputc(c, fp_out);
872
0
        if (c == '\n')
873
0
        {
874
0
          fputs(buf_string(prefix), fp_out);
875
0
        }
876
0
      }
877
0
    }
878
0
    else if (mutt_file_copy_bytes(fp_in, fp_out, body->length) == -1)
879
0
    {
880
0
      rc = 1;
881
0
      goto done;
882
0
    }
883
0
  }
884
885
0
  if ((cmflags & MUTT_CM_UPDATE) && ((cmflags & MUTT_CM_NOHEADER) == 0) &&
886
0
      (new_offset != -1))
887
0
  {
888
0
    body->offset = new_offset;
889
0
    mutt_body_free(&body->parts);
890
0
  }
891
892
0
done:
893
0
  buf_pool_release(&prefix);
894
0
  return rc;
895
0
}
896
897
/**
898
 * mutt_copy_message - Copy a message from a Mailbox
899
 * @param fp_out  FILE pointer to write to
900
 * @param e       Email
901
 * @param msg     Message
902
 * @param cmflags Flags, see #CopyMessageFlags
903
 * @param chflags Flags, see #CopyHeaderFlags
904
 * @param wraplen Width to wrap at (when chflags & CH_DISPLAY)
905
 * @retval  0 Success
906
 * @retval -1 Failure
907
 *
908
 * should be made to return -1 on fatal errors, and 1 on non-fatal errors
909
 * like partial decode, where it is worth displaying as much as possible
910
 */
911
int mutt_copy_message(FILE *fp_out, struct Email *e, struct Message *msg,
912
                      CopyMessageFlags cmflags, CopyHeaderFlags chflags, int wraplen)
913
0
{
914
0
  if (!msg || !e->body)
915
0
  {
916
0
    return -1;
917
0
  }
918
0
  if (fp_out == msg->fp)
919
0
  {
920
0
    mutt_debug(LL_DEBUG1, "trying to read/write from/to the same FILE*!\n");
921
0
    return -1;
922
0
  }
923
924
0
  int rc = mutt_copy_message_fp(fp_out, msg->fp, e, cmflags, chflags, wraplen);
925
0
  if ((rc == 0) && (ferror(fp_out) || feof(fp_out)))
926
0
  {
927
0
    mutt_debug(LL_DEBUG1, "failed to detect EOF!\n");
928
0
    rc = -1;
929
0
  }
930
0
  return rc;
931
0
}
932
933
/**
934
 * append_message - Appends a copy of the given message to a mailbox
935
 * @param dest    destination mailbox
936
 * @param fp_in    where to get input
937
 * @param src     source mailbox
938
 * @param e       Email being copied
939
 * @param cmflags Flags, see #CopyMessageFlags
940
 * @param chflags Flags, see #CopyHeaderFlags
941
 * @retval  0 Success
942
 * @retval -1 Error
943
 */
944
static int append_message(struct Mailbox *dest, FILE *fp_in, struct Mailbox *src,
945
                          struct Email *e, CopyMessageFlags cmflags, CopyHeaderFlags chflags)
946
0
{
947
0
  char buf[256] = { 0 };
948
0
  struct Message *msg = NULL;
949
0
  int rc;
950
951
0
  if (!mutt_file_seek(fp_in, e->offset, SEEK_SET))
952
0
    return -1;
953
0
  if (!fgets(buf, sizeof(buf), fp_in))
954
0
    return -1;
955
956
0
  msg = mx_msg_open_new(dest, e, is_from(buf, NULL, 0, NULL) ? MUTT_MSG_NO_FLAGS : MUTT_ADD_FROM);
957
0
  if (!msg)
958
0
    return -1;
959
0
  if ((dest->type == MUTT_MBOX) || (dest->type == MUTT_MMDF))
960
0
    chflags |= CH_FROM | CH_FORCE_FROM;
961
0
  chflags |= ((dest->type == MUTT_MAILDIR) ? CH_NOSTATUS : CH_UPDATE);
962
0
  rc = mutt_copy_message_fp(msg->fp, fp_in, e, cmflags, chflags, 0);
963
0
  if (mx_msg_commit(dest, msg) != 0)
964
0
    rc = -1;
965
966
#ifdef USE_NOTMUCH
967
  if (msg->committed_path && (dest->type == MUTT_MAILDIR) && (src->type == MUTT_NOTMUCH))
968
    nm_update_filename(src, NULL, msg->committed_path, e);
969
#endif
970
971
0
  mx_msg_close(dest, &msg);
972
0
  return rc;
973
0
}
974
975
/**
976
 * mutt_append_message - Append a message
977
 * @param m_dst   Destination Mailbox
978
 * @param m_src   Source Mailbox
979
 * @param e       Email
980
 * @param msg     Message
981
 * @param cmflags Flags, see #CopyMessageFlags
982
 * @param chflags Flags, see #CopyHeaderFlags
983
 * @retval  0 Success
984
 * @retval -1 Failure
985
 */
986
int mutt_append_message(struct Mailbox *m_dst, struct Mailbox *m_src,
987
                        struct Email *e, struct Message *msg,
988
                        CopyMessageFlags cmflags, CopyHeaderFlags chflags)
989
0
{
990
0
  if (!e)
991
0
    return -1;
992
993
0
  const bool own_msg = !msg;
994
0
  if (own_msg && !(msg = mx_msg_open(m_src, e)))
995
0
  {
996
0
    return -1;
997
0
  }
998
999
0
  int rc = append_message(m_dst, msg->fp, m_src, e, cmflags, chflags);
1000
0
  if (own_msg)
1001
0
  {
1002
0
    mx_msg_close(m_src, &msg);
1003
0
  }
1004
0
  return rc;
1005
0
}
1006
1007
/**
1008
 * copy_delete_attach - Copy a message, deleting marked attachments
1009
 * @param b           Email Body
1010
 * @param fp_in       FILE pointer to read from
1011
 * @param fp_out      FILE pointer to write to
1012
 * @param quoted_date Date stamp
1013
 * @retval  0 Success
1014
 * @retval -1 Failure
1015
 *
1016
 * This function copies a message body, while deleting _in_the_copy_
1017
 * any attachments which are marked for deletion.
1018
 * Nothing is changed in the original message -- this is left to the caller.
1019
 */
1020
static int copy_delete_attach(struct Body *b, FILE *fp_in, FILE *fp_out, const char *quoted_date)
1021
0
{
1022
0
  struct Body *part = NULL;
1023
1024
0
  for (part = b->parts; part; part = part->next)
1025
0
  {
1026
0
    if (part->deleted || part->parts)
1027
0
    {
1028
      /* Copy till start of this part */
1029
0
      if (mutt_file_copy_bytes(fp_in, fp_out, part->hdr_offset - ftello(fp_in)))
1030
0
      {
1031
0
        return -1;
1032
0
      }
1033
1034
0
      if (part->deleted)
1035
0
      {
1036
        /* If this is modified, count_delete_lines() needs to be changed too */
1037
0
        fprintf(fp_out,
1038
0
                "Content-Type: message/external-body; access-type=x-mutt-deleted;\n"
1039
0
                "\texpiration=%s; length=" OFF_T_FMT "\n"
1040
0
                "\n",
1041
0
                quoted_date, part->length);
1042
0
        if (ferror(fp_out))
1043
0
        {
1044
0
          return -1;
1045
0
        }
1046
1047
        /* Copy the original mime headers */
1048
0
        if (mutt_file_copy_bytes(fp_in, fp_out, part->offset - ftello(fp_in)))
1049
0
        {
1050
0
          return -1;
1051
0
        }
1052
1053
        /* Skip the deleted body */
1054
0
        if (!mutt_file_seek(fp_in, part->offset + part->length, SEEK_SET))
1055
0
        {
1056
0
          return -1;
1057
0
        }
1058
0
      }
1059
0
      else
1060
0
      {
1061
0
        if (copy_delete_attach(part, fp_in, fp_out, quoted_date))
1062
0
        {
1063
0
          return -1;
1064
0
        }
1065
0
      }
1066
0
    }
1067
0
  }
1068
1069
  /* Copy the last parts */
1070
0
  if (mutt_file_copy_bytes(fp_in, fp_out, b->offset + b->length - ftello(fp_in)))
1071
0
    return -1;
1072
1073
0
  return 0;
1074
0
}
1075
1076
/**
1077
 * address_header_decode - Parse an email's headers
1078
 * @param[out] h Array of header strings
1079
 * @retval 0 Success
1080
 * @retval 1 Failure
1081
 */
1082
static int address_header_decode(char **h)
1083
0
{
1084
0
  char *s = *h;
1085
0
  size_t l;
1086
0
  bool rp = false;
1087
1088
0
  switch (tolower((unsigned char) *s))
1089
0
  {
1090
0
    case 'b':
1091
0
    {
1092
0
      if (!(l = mutt_istr_startswith(s, "bcc:")))
1093
0
        return 0;
1094
0
      break;
1095
0
    }
1096
0
    case 'c':
1097
0
    {
1098
0
      if (!(l = mutt_istr_startswith(s, "cc:")))
1099
0
        return 0;
1100
0
      break;
1101
0
    }
1102
0
    case 'f':
1103
0
    {
1104
0
      if (!(l = mutt_istr_startswith(s, "from:")))
1105
0
        return 0;
1106
0
      break;
1107
0
    }
1108
0
    case 'm':
1109
0
    {
1110
0
      if (!(l = mutt_istr_startswith(s, "mail-followup-to:")))
1111
0
        return 0;
1112
0
      break;
1113
0
    }
1114
0
    case 'r':
1115
0
    {
1116
0
      if ((l = mutt_istr_startswith(s, "return-path:")))
1117
0
      {
1118
0
        rp = true;
1119
0
        break;
1120
0
      }
1121
0
      else if ((l = mutt_istr_startswith(s, "reply-to:")))
1122
0
      {
1123
0
        break;
1124
0
      }
1125
0
      return 0;
1126
0
    }
1127
0
    case 's':
1128
0
    {
1129
0
      if (!(l = mutt_istr_startswith(s, "sender:")))
1130
0
        return 0;
1131
0
      break;
1132
0
    }
1133
0
    case 't':
1134
0
    {
1135
0
      if (!(l = mutt_istr_startswith(s, "to:")))
1136
0
        return 0;
1137
0
      break;
1138
0
    }
1139
0
    default:
1140
0
      return 0;
1141
0
  }
1142
1143
0
  struct AddressList al = TAILQ_HEAD_INITIALIZER(al);
1144
0
  mutt_addrlist_parse(&al, s + l);
1145
0
  if (TAILQ_EMPTY(&al))
1146
0
    return 0;
1147
1148
0
  mutt_addrlist_to_local(&al);
1149
0
  rfc2047_decode_addrlist(&al);
1150
0
  struct Address *a = NULL;
1151
0
  TAILQ_FOREACH(a, &al, entries)
1152
0
  {
1153
0
    if (a->personal)
1154
0
    {
1155
0
      buf_dequote_comment(a->personal);
1156
0
    }
1157
0
  }
1158
1159
  /* angle brackets for return path are mandated by RFC5322,
1160
   * so leave Return-Path as-is */
1161
0
  if (rp)
1162
0
  {
1163
0
    *h = mutt_str_dup(s);
1164
0
  }
1165
0
  else
1166
0
  {
1167
0
    struct Buffer buf = { 0 };
1168
0
    (*h)[l - 1] = '\0';
1169
0
    mutt_addrlist_write_wrap(&al, &buf, *h);
1170
0
    buf_addch(&buf, '\n');
1171
0
    *h = buf.data;
1172
0
  }
1173
1174
0
  mutt_addrlist_clear(&al);
1175
1176
0
  FREE(&s);
1177
0
  return 1;
1178
0
}