3 * Duplicate the structure of an entire email
6 * Copyright (C) 1996-2000,2002,2014 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
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
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
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/>.
25 * @page copy Duplicate the structure of an entire email
27 * Duplicate the structure of an entire email
32 #include <inttypes.h> // IWYU pragma: keep
35 #include "mutt/mutt.h"
36 #include "address/lib.h"
37 #include "email/lib.h"
45 #include "mutt_window.h"
48 #include "ncrypt/ncrypt.h"
52 #include "notmuch/mutt_notmuch.h"
58 static int address_header_decode(char **h);
59 static int copy_delete_attach(struct Body *b, FILE *fp_in, FILE *fp_out, char *date);
62 * mutt_copy_hdr - Copy header from one file to another
63 * @param fp_in FILE pointer to read from
64 * @param fp_out FILE pointer to write to
65 * @param off_start Offset to start from
66 * @param off_end Offset to finish at
67 * @param chflags Flags, see #CopyHeaderFlags
68 * @param prefix Prefix for quoting headers
69 * @param wraplen Width to wrap at (when chflags & CH_DISPLAY)
73 * Ok, the only reason for not merging this with mutt_copy_header() below is to
74 * avoid creating a Email structure in message_handler(). Also, this one will
75 * wrap headers much more aggressively than the other one.
77 int mutt_copy_hdr(FILE *fp_in, FILE *fp_out, LOFF_T off_start, LOFF_T off_end,
78 CopyHeaderFlags chflags, const char *prefix, int wraplen)
81 bool this_is_from = false;
83 char buf[1024]; /* should be long enough to get most fields in one pass */
85 char **headers = NULL;
88 char *this_one = NULL;
89 size_t this_one_len = 0;
95 if (ftello(fp_in) != off_start)
96 if (fseeko(fp_in, off_start, SEEK_SET) < 0)
102 if ((chflags & (CH_REORDER | CH_WEED | CH_MIME | CH_DECODE | CH_PREFIX | CH_WEED_DELIVERED)) == 0)
104 /* Without these flags to complicate things
105 * we can do a more efficient line to line copying */
106 while (ftello(fp_in) < off_end)
108 nl = strchr(buf, '\n');
110 if (!fgets(buf, sizeof(buf), fp_in))
113 /* Is it the beginning of a header? */
114 if (nl && (buf[0] != ' ') && (buf[0] != '\t'))
117 if (!from && mutt_str_startswith(buf, "From ", CASE_MATCH))
119 if ((chflags & CH_FROM) == 0)
123 else if ((chflags & CH_NOQFROM) && mutt_str_startswith(buf, ">From ", CASE_IGNORE))
126 else if ((buf[0] == '\n') || ((buf[0] == '\r') && (buf[1] == '\n')))
127 break; /* end of header */
129 if ((chflags & (CH_UPDATE | CH_XMIT | CH_NOSTATUS)) &&
130 (mutt_str_startswith(buf, "Status:", CASE_IGNORE) ||
131 mutt_str_startswith(buf, "X-Status:", CASE_IGNORE)))
135 if ((chflags & (CH_UPDATE_LEN | CH_XMIT | CH_NOLEN)) &&
136 (mutt_str_startswith(buf, "Content-Length:", CASE_IGNORE) ||
137 mutt_str_startswith(buf, "Lines:", CASE_IGNORE)))
141 if ((chflags & CH_UPDATE_REFS) && mutt_str_startswith(buf, "References:", CASE_IGNORE))
143 if ((chflags & CH_UPDATE_IRT) && mutt_str_startswith(buf, "In-Reply-To:", CASE_IGNORE))
145 if (chflags & CH_UPDATE_LABEL && mutt_str_startswith(buf, "X-Label:", CASE_IGNORE))
147 if ((chflags & CH_UPDATE_SUBJECT) && mutt_str_startswith(buf, "Subject:", CASE_IGNORE))
153 if (!ignore && (fputs(buf, fp_out) == EOF))
163 /* We are going to read and collect the headers in an array
164 * so we are able to do re-ordering.
165 * First count the number of entries in the array */
166 if (chflags & CH_REORDER)
168 struct ListNode *np = NULL;
169 STAILQ_FOREACH(np, &HeaderOrderList, entries)
171 mutt_debug(LL_DEBUG3, "Reorder list: %s\n", np->data);
176 mutt_debug(LL_DEBUG1, "WEED is %s\n", (chflags & CH_WEED) ? "Set" : "Not");
178 headers = mutt_mem_calloc(hdr_count, sizeof(char *));
180 /* Read all the headers into the array */
181 while (ftello(fp_in) < off_end)
183 nl = strchr(buf, '\n');
186 if (!fgets(buf, sizeof(buf), fp_in))
189 /* Is it the beginning of a header? */
190 if (nl && (buf[0] != ' ') && (buf[0] != '\t'))
192 /* Do we have anything pending? */
195 if (chflags & CH_DECODE)
197 if (address_header_decode(&this_one) == 0)
198 rfc2047_decode(&this_one);
199 this_one_len = mutt_str_strlen(this_one);
201 /* Convert CRLF line endings to LF */
202 if ((this_one_len > 2) && (this_one[this_one_len - 2] == '\r') &&
203 (this_one[this_one_len - 1] == '\n'))
205 this_one[this_one_len - 2] = '\n';
206 this_one[this_one_len - 1] = '\0';
211 headers[x] = this_one;
214 int hlen = mutt_str_strlen(headers[x]);
216 mutt_mem_realloc(&headers[x], hlen + this_one_len + sizeof(char));
217 strcat(headers[x] + hlen, this_one);
225 this_is_from = false;
226 if (!from && mutt_str_startswith(buf, "From ", CASE_MATCH))
228 if ((chflags & CH_FROM) == 0)
233 else if ((buf[0] == '\n') || ((buf[0] == '\r') && (buf[1] == '\n')))
234 break; /* end of header */
236 /* note: CH_FROM takes precedence over header weeding. */
237 if (!((chflags & CH_FROM) && (chflags & CH_FORCE_FROM) && this_is_from) &&
238 (chflags & CH_WEED) && mutt_matches_ignore(buf))
242 if ((chflags & CH_WEED_DELIVERED) && mutt_str_startswith(buf, "Delivered-To:", CASE_IGNORE))
246 if ((chflags & (CH_UPDATE | CH_XMIT | CH_NOSTATUS)) &&
247 (mutt_str_startswith(buf, "Status:", CASE_IGNORE) ||
248 mutt_str_startswith(buf, "X-Status:", CASE_IGNORE)))
252 if ((chflags & (CH_UPDATE_LEN | CH_XMIT | CH_NOLEN)) &&
253 (mutt_str_startswith(buf, "Content-Length:", CASE_IGNORE) ||
254 mutt_str_startswith(buf, "Lines:", CASE_IGNORE)))
258 if ((chflags & CH_MIME))
260 if (mutt_str_startswith(buf, "mime-version:", CASE_IGNORE))
264 size_t plen = mutt_str_startswith(buf, "content-", CASE_IGNORE);
265 if ((plen != 0) && (mutt_str_startswith(buf + plen, "transfer-encoding:", CASE_IGNORE) ||
266 mutt_str_startswith(buf + plen, "type:", CASE_IGNORE)))
271 if ((chflags & CH_UPDATE_REFS) && mutt_str_startswith(buf, "References:", CASE_IGNORE))
273 if ((chflags & CH_UPDATE_IRT) && mutt_str_startswith(buf, "In-Reply-To:", CASE_IGNORE))
275 if ((chflags & CH_UPDATE_LABEL) && mutt_str_startswith(buf, "X-Label:", CASE_IGNORE))
277 if ((chflags & CH_UPDATE_SUBJECT) && mutt_str_startswith(buf, "Subject:", CASE_IGNORE))
280 /* Find x -- the array entry where this header is to be saved */
281 if (chflags & CH_REORDER)
283 struct ListNode *np = NULL;
285 STAILQ_FOREACH(np, &HeaderOrderList, entries)
288 if (mutt_str_startswith(buf, np->data, CASE_IGNORE))
290 mutt_debug(LL_DEBUG2, "Reorder: %s matches %s", np->data, buf);
297 } /* If beginning of header */
301 mutt_debug(LL_DEBUG2, "Reorder: x = %d; hdr_count = %d\n", x, hdr_count);
304 this_one = mutt_str_strdup(buf);
305 this_one_len = mutt_str_strlen(this_one);
309 size_t blen = mutt_str_strlen(buf);
311 mutt_mem_realloc(&this_one, this_one_len + blen + sizeof(char));
312 strcat(this_one + this_one_len, buf);
313 this_one_len += blen;
316 } /* while (ftello (fp_in) < off_end) */
318 /* Do we have anything pending? -- XXX, same code as in above in the loop. */
321 if (chflags & CH_DECODE)
323 if (address_header_decode(&this_one) == 0)
324 rfc2047_decode(&this_one);
325 this_one_len = mutt_str_strlen(this_one);
329 headers[x] = this_one;
332 int hlen = mutt_str_strlen(headers[x]);
334 mutt_mem_realloc(&headers[x], hlen + this_one_len + sizeof(char));
335 strcat(headers[x] + hlen, this_one);
342 /* Now output the headers in order */
343 for (x = 0; x < hdr_count; x++)
347 /* We couldn't do the prefixing when reading because RFC2047
348 * decoding may have concatenated lines. */
349 if (chflags & (CH_DECODE | CH_PREFIX))
351 const char *pre = (chflags & CH_PREFIX) ? prefix : NULL;
352 wraplen = mutt_window_wrap_cols(wraplen, C_Wrap);
354 if (mutt_write_one_header(fp_out, 0, headers[x], pre, wraplen, chflags) == -1)
362 if (fputs(headers[x], fp_out) == EOF)
371 /* Free in a separate loop to be sure that all headers are freed
372 * in case of error. */
373 for (x = 0; x < hdr_count; x++)
383 * mutt_copy_header - Copy Email header
384 * @param fp_in FILE pointer to read from
386 * @param fp_out FILE pointer to write to
387 * @param chflags See #CopyHeaderFlags
388 * @param prefix Prefix for quoting headers (if #CH_PREFIX is set)
389 * @param wraplen Width to wrap at (when chflags & CH_DISPLAY)
393 int mutt_copy_header(FILE *fp_in, struct Email *e, FILE *fp_out,
394 CopyHeaderFlags chflags, const char *prefix, int wraplen)
396 char *temp_hdr = NULL;
400 chflags |= ((e->env->changed & MUTT_ENV_CHANGED_IRT) ? CH_UPDATE_IRT : 0) |
401 ((e->env->changed & MUTT_ENV_CHANGED_REFS) ? CH_UPDATE_REFS : 0) |
402 ((e->env->changed & MUTT_ENV_CHANGED_XLABEL) ? CH_UPDATE_LABEL : 0) |
403 ((e->env->changed & MUTT_ENV_CHANGED_SUBJECT) ? CH_UPDATE_SUBJECT : 0);
406 if (mutt_copy_hdr(fp_in, fp_out, e->offset, e->content->offset, chflags,
407 prefix, wraplen) == -1)
410 if (chflags & CH_TXTPLAIN)
414 fputs("MIME-Version: 1.0\n", fp_out);
415 fputs("Content-Transfer-Encoding: 8bit\n", fp_out);
416 fputs("Content-Type: text/plain; charset=", fp_out);
417 mutt_ch_canonical_charset(chsbuf, sizeof(chsbuf), C_Charset ? C_Charset : "us-ascii");
418 mutt_addr_cat(buf, sizeof(buf), chsbuf, MimeSpecials);
423 if ((chflags & CH_UPDATE_IRT) && !STAILQ_EMPTY(&e->env->in_reply_to))
425 fputs("In-Reply-To:", fp_out);
426 struct ListNode *np = NULL;
427 STAILQ_FOREACH(np, &e->env->in_reply_to, entries)
430 fputs(np->data, fp_out);
435 if ((chflags & CH_UPDATE_REFS) && !STAILQ_EMPTY(&e->env->references))
437 fputs("References:", fp_out);
438 mutt_write_references(&e->env->references, fp_out, 0);
442 if ((chflags & CH_UPDATE) && ((chflags & CH_NOSTATUS) == 0))
444 if (e->old || e->read)
446 fputs("Status: ", fp_out);
454 if (e->flagged || e->replied)
456 fputs("X-Status: ", fp_out);
465 if (chflags & CH_UPDATE_LEN && ((chflags & CH_NOLEN) == 0))
467 fprintf(fp_out, "Content-Length: " OFF_T_FMT "\n", e->content->length);
468 if ((e->lines != 0) || (e->content->length == 0))
469 fprintf(fp_out, "Lines: %d\n", e->lines);
473 if (chflags & CH_VIRTUAL)
475 /* Add some fake headers based on notmuch data */
476 char *folder = nm_email_get_folder(e);
477 if (folder && !(C_Weed && mutt_matches_ignore("folder")))
480 mutt_str_strfcpy(buf, folder, sizeof(buf));
481 mutt_pretty_mailbox(buf, sizeof(buf));
483 fputs("Folder: ", fp_out);
489 char *tags = driver_tags_get(&e->tags);
490 if (tags && !(C_Weed && mutt_matches_ignore("tags")))
492 fputs("Tags: ", fp_out);
498 if ((chflags & CH_UPDATE_LABEL) && e->env->x_label)
500 temp_hdr = e->env->x_label;
501 /* env->x_label isn't currently stored with direct references elsewhere.
502 * Context->label_hash strdups the keys. But to be safe, encode a copy */
503 if (!(chflags & CH_DECODE))
505 temp_hdr = mutt_str_strdup(temp_hdr);
506 rfc2047_encode(&temp_hdr, NULL, sizeof("X-Label:"), C_SendCharset);
508 if (mutt_write_one_header(fp_out, "X-Label", temp_hdr, (chflags & CH_PREFIX) ? prefix : 0,
509 mutt_window_wrap_cols(wraplen, C_Wrap), chflags) == -1)
513 if (!(chflags & CH_DECODE))
517 if ((chflags & CH_UPDATE_SUBJECT) && e->env->subject)
519 temp_hdr = e->env->subject;
520 /* env->subject is directly referenced in Context->subj_hash, so we
521 * have to be careful not to encode (and thus free) that memory. */
522 if (!(chflags & CH_DECODE))
524 temp_hdr = mutt_str_strdup(temp_hdr);
525 rfc2047_encode(&temp_hdr, NULL, sizeof("Subject:"), C_SendCharset);
527 if (mutt_write_one_header(fp_out, "Subject", temp_hdr, (chflags & CH_PREFIX) ? prefix : 0,
528 mutt_window_wrap_cols(wraplen, C_Wrap), chflags) == -1)
532 if (!(chflags & CH_DECODE))
536 if ((chflags & CH_NONEWLINE) == 0)
538 if (chflags & CH_PREFIX)
539 fputs(prefix, fp_out);
540 fputc('\n', fp_out); /* add header terminator */
543 if (ferror(fp_out) || feof(fp_out))
550 * count_delete_lines - Count lines to be deleted in this email body
551 * @param fp FILE pointer to read from
552 * @param b Email Body
553 * @param length Number of bytes to be deleted
554 * @param datelen Length of the date
555 * @retval num Number of lines to be deleted
557 * Count the number of lines and bytes to be deleted in this body
559 static int count_delete_lines(FILE *fp, struct Body *b, LOFF_T *length, size_t datelen)
565 fseeko(fp, b->offset, SEEK_SET);
566 for (long l = b->length; l; l--)
568 const int ch = getc(fp);
575 *length -= b->length - (84 + datelen);
576 /* Count the number of digits exceeding the first one to write the size */
577 for (long l = 10; b->length >= l; l *= 10)
582 for (b = b->parts; b; b = b->next)
583 dellines += count_delete_lines(fp, b, length, datelen);
589 * mutt_copy_message_fp - make a copy of a message from a FILE pointer
590 * @param fp_out Where to write output
591 * @param fp_in Where to get input
592 * @param e Email being copied
593 * @param cmflags Flags, see #CopyMessageFlags
594 * @param chflags Flags, see #CopyHeaderFlags
595 * @param wraplen Width to wrap at (when chflags & CH_DISPLAY)
599 int mutt_copy_message_fp(FILE *fp_out, FILE *fp_in, struct Email *e,
600 CopyMessageFlags cmflags, CopyHeaderFlags chflags, int wraplen)
602 struct Body *body = e->content;
604 LOFF_T new_offset = -1;
607 if (cmflags & MUTT_CM_PREFIX)
610 mutt_str_strfcpy(prefix, ">", sizeof(prefix));
612 mutt_make_string(prefix, sizeof(prefix), wraplen, NONULL(C_IndentString),
613 Context, Context->mailbox, e);
616 if ((cmflags & MUTT_CM_NOHEADER) == 0)
618 if (cmflags & MUTT_CM_PREFIX)
619 chflags |= CH_PREFIX;
621 else if (e->attach_del && (chflags & CH_UPDATE_LEN))
624 LOFF_T new_length = body->length;
627 mutt_date_make_date(date, sizeof(date));
628 int dlen = mutt_str_strlen(date);
633 date[dlen - 1] = '\"';
635 /* Count the number of lines and bytes to be deleted */
636 fseeko(fp_in, body->offset, SEEK_SET);
637 new_lines = e->lines - count_delete_lines(fp_in, body, &new_length, dlen);
639 /* Copy the headers */
640 if (mutt_copy_header(fp_in, e, fp_out, chflags | CH_NOLEN | CH_NONEWLINE, NULL, wraplen))
642 fprintf(fp_out, "Content-Length: " OFF_T_FMT "\n", new_length);
646 fprintf(fp_out, "Lines: %d\n", new_lines);
649 if (ferror(fp_out) || feof(fp_out))
651 new_offset = ftello(fp_out);
654 if (fseeko(fp_in, body->offset, SEEK_SET) < 0)
656 if (copy_delete_attach(body, fp_in, fp_out, date))
659 LOFF_T fail = ((ftello(fp_out) - new_offset) - new_length);
662 mutt_error(ngettext("The length calculation was wrong by %ld byte",
663 "The length calculation was wrong by %ld bytes", fail),
668 /* Update original message if we are sync'ing a mailfolder */
669 if (cmflags & MUTT_CM_UPDATE)
671 e->attach_del = false;
672 e->lines = new_lines;
673 body->offset = new_offset;
675 /* update the total size of the mailbox to reflect this deletion */
676 Context->mailbox->size -= body->length - new_length;
677 /* if the message is visible, update the visible size of the mailbox as well. */
678 if (Context->mailbox->v2r[e->msgno] != -1)
679 Context->vsize -= body->length - new_length;
681 body->length = new_length;
682 mutt_body_free(&body->parts);
688 if (mutt_copy_header(fp_in, e, fp_out, chflags,
689 (chflags & CH_PREFIX) ? prefix : NULL, wraplen) == -1)
694 new_offset = ftello(fp_out);
697 if (cmflags & MUTT_CM_DECODE)
699 /* now make a text/plain version of the message */
700 struct State s = { 0 };
703 if (cmflags & MUTT_CM_PREFIX)
705 if (cmflags & MUTT_CM_DISPLAY)
707 s.flags |= MUTT_DISPLAY;
710 if (cmflags & MUTT_CM_PRINTING)
711 s.flags |= MUTT_PRINTING;
712 if (cmflags & MUTT_CM_WEED)
713 s.flags |= MUTT_WEED;
714 if (cmflags & MUTT_CM_CHARCONV)
715 s.flags |= MUTT_CHARCONV;
716 if (cmflags & MUTT_CM_REPLYING)
717 s.flags |= MUTT_REPLYING;
719 if ((WithCrypto != 0) && cmflags & MUTT_CM_VERIFY)
720 s.flags |= MUTT_VERIFY;
722 rc = mutt_body_handler(body, &s);
724 else if ((WithCrypto != 0) && (cmflags & MUTT_CM_DECODE_CRYPT) && (e->security & SEC_ENCRYPT))
726 struct Body *cur = NULL;
729 if (((WithCrypto & APPLICATION_PGP) != 0) && (cmflags & MUTT_CM_DECODE_PGP) &&
730 (e->security & APPLICATION_PGP) && (e->content->type == TYPE_MULTIPART))
732 if (crypt_pgp_decrypt_mime(fp_in, &fp, e->content, &cur))
734 fputs("MIME-Version: 1.0\n", fp_out);
737 if (((WithCrypto & APPLICATION_SMIME) != 0) && (cmflags & MUTT_CM_DECODE_SMIME) &&
738 (e->security & APPLICATION_SMIME) && (e->content->type == TYPE_APPLICATION))
740 if (crypt_smime_decrypt_mime(fp_in, &fp, e->content, &cur))
746 mutt_error(_("No decryption engine available for message"));
750 mutt_write_mime_header(cur, fp_out);
753 if (fseeko(fp, cur->offset, SEEK_SET) < 0)
755 if (mutt_file_copy_bytes(fp, fp_out, cur->length) == -1)
757 mutt_file_fclose(&fp);
758 mutt_body_free(&cur);
761 mutt_body_free(&cur);
762 mutt_file_fclose(&fp);
766 if (fseeko(fp_in, body->offset, SEEK_SET) < 0)
768 if (cmflags & MUTT_CM_PREFIX)
771 size_t bytes = body->length;
773 fputs(prefix, fp_out);
775 while (((c = fgetc(fp_in)) != EOF) && bytes--)
780 fputs(prefix, fp_out);
784 else if (mutt_file_copy_bytes(fp_in, fp_out, body->length) == -1)
788 if ((cmflags & MUTT_CM_UPDATE) && ((cmflags & MUTT_CM_NOHEADER) == 0) &&
791 body->offset = new_offset;
792 mutt_body_free(&body->parts);
799 * mutt_copy_message - Copy a message from a Mailbox
800 * @param fp_out FILE pointer to write to
801 * @param m Source mailbox
803 * @param cmflags Flags, see #CopyMessageFlags
804 * @param chflags Flags, see #CopyHeaderFlags
805 * @param wraplen Width to wrap at (when chflags & CH_DISPLAY)
809 * should be made to return -1 on fatal errors, and 1 on non-fatal errors
810 * like partial decode, where it is worth displaying as much as possible
812 int mutt_copy_message(FILE *fp_out, struct Mailbox *m, struct Email *e,
813 CopyMessageFlags cmflags, CopyHeaderFlags chflags, int wraplen)
815 struct Message *msg = mx_msg_open(m, e->msgno);
820 int rc = mutt_copy_message_fp(fp_out, msg->fp, e, cmflags, chflags, wraplen);
821 if ((rc == 0) && (ferror(fp_out) || feof(fp_out)))
823 mutt_debug(LL_DEBUG1, "failed to detect EOF!\n");
826 mx_msg_close(m, &msg);
831 * append_message - appends a copy of the given message to a mailbox
832 * @param dest destination mailbox
833 * @param fp_in where to get input
834 * @param src source mailbox
835 * @param e Email being copied
836 * @param cmflags Flags, see #CopyMessageFlags
837 * @param chflags Flags, see #CopyHeaderFlags
841 static int append_message(struct Mailbox *dest, FILE *fp_in, struct Mailbox *src,
842 struct Email *e, CopyMessageFlags cmflags, CopyHeaderFlags chflags)
845 struct Message *msg = NULL;
848 if (fseeko(fp_in, e->offset, SEEK_SET) < 0)
850 if (!fgets(buf, sizeof(buf), fp_in))
853 msg = mx_msg_open_new(dest, e, is_from(buf, NULL, 0, NULL) ? MUTT_MSG_NO_FLAGS : MUTT_ADD_FROM);
856 if ((dest->magic == MUTT_MBOX) || (dest->magic == MUTT_MMDF))
857 chflags |= CH_FROM | CH_FORCE_FROM;
858 chflags |= ((dest->magic == MUTT_MAILDIR) ? CH_NOSTATUS : CH_UPDATE);
859 rc = mutt_copy_message_fp(msg->fp, fp_in, e, cmflags, chflags, 0);
860 if (mx_msg_commit(dest, msg) != 0)
864 if (msg->committed_path && (dest->magic == MUTT_MAILDIR) && (src->magic == MUTT_NOTMUCH))
865 nm_update_filename(src, NULL, msg->committed_path, e);
868 mx_msg_close(dest, &msg);
873 * mutt_append_message - Append a message
874 * @param dest Destination Mailbox
875 * @param src Source Mailbox
877 * @param cmflags Flags, see #CopyMessageFlags
878 * @param chflags Flags, see #CopyHeaderFlags
882 int mutt_append_message(struct Mailbox *dest, struct Mailbox *src, struct Email *e,
883 CopyMessageFlags cmflags, CopyHeaderFlags chflags)
885 struct Message *msg = mx_msg_open(src, e->msgno);
888 int rc = append_message(dest, msg->fp, src, e, cmflags, chflags);
889 mx_msg_close(src, &msg);
894 * copy_delete_attach - Copy a message, deleting marked attachments
895 * @param b Email Body
896 * @param fp_in FILE pointer to read from
897 * @param fp_out FILE pointer to write to
898 * @param date Date stamp
902 * This function copies a message body, while deleting _in_the_copy_
903 * any attachments which are marked for deletion.
904 * Nothing is changed in the original message -- this is left to the caller.
906 static int copy_delete_attach(struct Body *b, FILE *fp_in, FILE *fp_out, char *date)
908 struct Body *part = NULL;
910 for (part = b->parts; part; part = part->next)
912 if (part->deleted || part->parts)
914 /* Copy till start of this part */
915 if (mutt_file_copy_bytes(fp_in, fp_out, part->hdr_offset - ftello(fp_in)))
922 "Content-Type: message/external-body; access-type=x-mutt-deleted;\n"
923 "\texpiration=%s; length=" OFF_T_FMT "\n"
925 date + 5, part->length);
929 /* Copy the original mime headers */
930 if (mutt_file_copy_bytes(fp_in, fp_out, part->offset - ftello(fp_in)))
933 /* Skip the deleted body */
934 fseeko(fp_in, part->offset + part->length, SEEK_SET);
938 if (copy_delete_attach(part, fp_in, fp_out, date))
944 /* Copy the last parts */
945 if (mutt_file_copy_bytes(fp_in, fp_out, b->offset + b->length - ftello(fp_in)))
952 * format_address_header - Write address headers to a buffer
953 * @param[out] h Array of header strings
954 * @param[in] al AddressList
956 * This function is the equivalent of mutt_write_addrlist(), but writes to
957 * a buffer instead of writing to a stream. mutt_write_addrlist could be
958 * re-used if we wouldn't store all the decoded headers in a huge array, first.
962 static void format_address_header(char **h, struct AddressList *al)
968 size_t linelen, buflen, plen;
970 linelen = mutt_str_strlen(*h);
972 buflen = linelen + 3;
974 mutt_mem_realloc(h, buflen);
975 struct Address *first = TAILQ_FIRST(al);
976 struct Address *a = NULL;
977 TAILQ_FOREACH(a, al, entries)
982 const size_t l = mutt_addr_write(buf, sizeof(buf), a, false);
984 if (a != first && (linelen + l > 74))
986 strcpy(cbuf, "\n\t");
998 if (!a->group && TAILQ_NEXT(a, entries) && TAILQ_NEXT(a, entries)->mailbox)
1005 const size_t cbuflen = mutt_str_strlen(cbuf);
1006 const size_t c2buflen = mutt_str_strlen(c2buf);
1007 buflen += l + cbuflen + c2buflen;
1008 mutt_mem_realloc(h, buflen);
1010 strcat(p + plen, cbuf);
1012 strcat(p + plen, buf);
1014 strcat(p + plen, c2buf);
1018 /* Space for this was allocated in the beginning of this function. */
1019 strcat(p + plen, "\n");
1023 * address_header_decode - Parse an email's headers
1024 * @param[out] h Array of header strings
1028 static int address_header_decode(char **h)
1034 switch (tolower((unsigned char) *s))
1038 if (!(l = mutt_str_startswith(s, "bcc:", CASE_IGNORE)))
1044 if (!(l = mutt_str_startswith(s, "cc:", CASE_IGNORE)))
1050 if (!(l = mutt_str_startswith(s, "from:", CASE_IGNORE)))
1056 if (!(l = mutt_str_startswith(s, "mail-followup-to:", CASE_IGNORE)))
1062 if ((l = mutt_str_startswith(s, "return-path:", CASE_IGNORE)))
1067 else if ((l = mutt_str_startswith(s, "reply-to:", CASE_IGNORE)))
1075 if (!(l = mutt_str_startswith(s, "sender:", CASE_IGNORE)))
1081 if (!(l = mutt_str_startswith(s, "to:", CASE_IGNORE)))
1089 struct AddressList al = TAILQ_HEAD_INITIALIZER(al);
1090 mutt_addrlist_parse(&al, s + l);
1091 if (TAILQ_EMPTY(&al))
1094 mutt_addrlist_to_local(&al);
1095 rfc2047_decode_addrlist(&al);
1096 struct Address *a = NULL;
1097 TAILQ_FOREACH(a, &al, entries)
1101 mutt_str_dequote_comment(a->personal);
1105 /* angle brackets for return path are mandated by RFC5322,
1106 * so leave Return-Path as-is */
1108 *h = mutt_str_strdup(s);
1111 *h = mutt_mem_calloc(1, l + 2);
1112 mutt_str_strfcpy(*h, s, l + 1);
1113 format_address_header(h, &al);
1116 mutt_addrlist_clear(&al);