Coverage Report

Created: 2023-09-25 07:17

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