/** * @file * Duplicate the structure of an entire email * * @authors * Copyright (C) 1996-2000,2002,2014 Michael R. Elkins * Copyright (C) 2019 Pietro Cerutti * * @copyright * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ /** * @page neo_copy Duplicate the structure of an entire email * * Duplicate the structure of an entire email */ #include "config.h" #include #include // IWYU pragma: keep #include #include #include #include "mutt/lib.h" #include "address/lib.h" #include "config/lib.h" #include "email/lib.h" #include "core/lib.h" #include "gui/lib.h" #include "mutt.h" #include "copy.h" #include "index/lib.h" #include "ncrypt/lib.h" #include "pager/lib.h" #include "send/lib.h" #include "format_flags.h" #include "globals.h" // IWYU pragma: keep #include "handler.h" #include "hdrline.h" #include "mx.h" #ifdef USE_NOTMUCH #include "notmuch/lib.h" #include "muttlib.h" #endif #ifdef ENABLE_NLS #include #endif static int address_header_decode(char **h); static int copy_delete_attach(struct Body *b, FILE *fp_in, FILE *fp_out, const char *quoted_date); ARRAY_HEAD(HeaderArray, char *); /** * add_one_header - Add a header to a Headers array * @param headers Headers array * @param pos Position to insert new header * @param value Text to insert * * If a header already exists in that position, the new text will be * concatenated on the old. */ static void add_one_header(struct HeaderArray *headers, size_t pos, char *value) { char **old = ARRAY_GET(headers, pos); if (old && *old) { char *new_value = NULL; mutt_str_asprintf(&new_value, "%s%s", *old, value); FREE(old); FREE(&value); value = new_value; } ARRAY_SET(headers, pos, value); } /** * mutt_copy_hdr - Copy header from one file to another * @param fp_in FILE pointer to read from * @param fp_out FILE pointer to write to * @param off_start Offset to start from * @param off_end Offset to finish at * @param chflags Flags, see #CopyHeaderFlags * @param prefix Prefix for quoting headers * @param wraplen Width to wrap at (when chflags & CH_DISPLAY) * @retval 0 Success * @retval -1 Failure * * Ok, the only reason for not merging this with mutt_copy_header() below is to * avoid creating a Email structure in message_handler(). Also, this one will * wrap headers much more aggressively than the other one. */ int mutt_copy_hdr(FILE *fp_in, FILE *fp_out, LOFF_T off_start, LOFF_T off_end, CopyHeaderFlags chflags, const char *prefix, int wraplen) { bool from = false; bool this_is_from = false; bool ignore = false; char buf[1024] = { 0 }; /* should be long enough to get most fields in one pass */ char *nl = NULL; struct HeaderArray headers = ARRAY_HEAD_INITIALIZER; int hdr_count; int x; char *this_one = NULL; size_t this_one_len = 0; if (off_start < 0) return -1; if (ftello(fp_in) != off_start) if (!mutt_file_seek(fp_in, off_start, SEEK_SET)) return -1; buf[0] = '\n'; buf[1] = '\0'; if ((chflags & (CH_REORDER | CH_WEED | CH_MIME | CH_DECODE | CH_PREFIX | CH_WEED_DELIVERED)) == 0) { /* Without these flags to complicate things * we can do a more efficient line to line copying */ while (ftello(fp_in) < off_end) { nl = strchr(buf, '\n'); if (!fgets(buf, sizeof(buf), fp_in)) break; /* Is it the beginning of a header? */ if (nl && (buf[0] != ' ') && (buf[0] != '\t')) { ignore = true; if (!from && mutt_str_startswith(buf, "From ")) { if ((chflags & CH_FROM) == 0) continue; from = true; } else if ((chflags & CH_NOQFROM) && mutt_istr_startswith(buf, ">From ")) { continue; } else if ((buf[0] == '\n') || ((buf[0] == '\r') && (buf[1] == '\n'))) { break; /* end of header */ } if ((chflags & (CH_UPDATE | CH_XMIT | CH_NOSTATUS)) && (mutt_istr_startswith(buf, "Status:") || mutt_istr_startswith(buf, "X-Status:"))) { continue; } if ((chflags & (CH_UPDATE_LEN | CH_XMIT | CH_NOLEN)) && (mutt_istr_startswith(buf, "Content-Length:") || mutt_istr_startswith(buf, "Lines:"))) { continue; } if ((chflags & CH_UPDATE_REFS) && mutt_istr_startswith(buf, "References:")) { continue; } if ((chflags & CH_UPDATE_IRT) && mutt_istr_startswith(buf, "In-Reply-To:")) { continue; } if (chflags & CH_UPDATE_LABEL && mutt_istr_startswith(buf, "X-Label:")) continue; if ((chflags & CH_UPDATE_SUBJECT) && mutt_istr_startswith(buf, "Subject:")) { continue; } ignore = false; } if (!ignore && (fputs(buf, fp_out) == EOF)) return -1; } return 0; } hdr_count = 1; x = 0; /* We are going to read and collect the headers in an array * so we are able to do re-ordering. * First count the number of entries in the array */ if (chflags & CH_REORDER) { struct ListNode *np = NULL; STAILQ_FOREACH(np, &HeaderOrderList, entries) { mutt_debug(LL_DEBUG3, "Reorder list: %s\n", np->data); hdr_count++; } } mutt_debug(LL_DEBUG1, "WEED is %sset\n", (chflags & CH_WEED) ? "" : "not "); ARRAY_RESERVE(&headers, hdr_count); /* Read all the headers into the array */ while (ftello(fp_in) < off_end) { nl = strchr(buf, '\n'); /* Read a line */ if (!fgets(buf, sizeof(buf), fp_in)) break; /* Is it the beginning of a header? */ if (nl && (buf[0] != ' ') && (buf[0] != '\t')) { /* Do we have anything pending? */ if (this_one) { if (chflags & CH_DECODE) { if (address_header_decode(&this_one) == 0) rfc2047_decode(&this_one); this_one_len = mutt_str_len(this_one); /* Convert CRLF line endings to LF */ if ((this_one_len > 2) && (this_one[this_one_len - 2] == '\r') && (this_one[this_one_len - 1] == '\n')) { this_one[this_one_len - 2] = '\n'; this_one[this_one_len - 1] = '\0'; } } add_one_header(&headers, x, this_one); this_one = NULL; } ignore = true; this_is_from = false; if (!from && mutt_str_startswith(buf, "From ")) { if ((chflags & CH_FROM) == 0) continue; this_is_from = true; from = true; } else if ((buf[0] == '\n') || ((buf[0] == '\r') && (buf[1] == '\n'))) { break; /* end of header */ } /* note: CH_FROM takes precedence over header weeding. */ if (!((chflags & CH_FROM) && (chflags & CH_FORCE_FROM) && this_is_from) && (chflags & CH_WEED) && mutt_matches_ignore(buf)) { continue; } if ((chflags & CH_WEED_DELIVERED) && mutt_istr_startswith(buf, "Delivered-To:")) { continue; } if ((chflags & (CH_UPDATE | CH_XMIT | CH_NOSTATUS)) && (mutt_istr_startswith(buf, "Status:") || mutt_istr_startswith(buf, "X-Status:"))) { continue; } if ((chflags & (CH_UPDATE_LEN | CH_XMIT | CH_NOLEN)) && (mutt_istr_startswith(buf, "Content-Length:") || mutt_istr_startswith(buf, "Lines:"))) { continue; } if ((chflags & CH_MIME)) { if (mutt_istr_startswith(buf, "mime-version:")) { continue; } size_t plen = mutt_istr_startswith(buf, "content-"); if ((plen != 0) && (mutt_istr_startswith(buf + plen, "transfer-encoding:") || mutt_istr_startswith(buf + plen, "type:"))) { continue; } } if ((chflags & CH_UPDATE_REFS) && mutt_istr_startswith(buf, "References:")) { continue; } if ((chflags & CH_UPDATE_IRT) && mutt_istr_startswith(buf, "In-Reply-To:")) { continue; } if ((chflags & CH_UPDATE_LABEL) && mutt_istr_startswith(buf, "X-Label:")) continue; if ((chflags & CH_UPDATE_SUBJECT) && mutt_istr_startswith(buf, "Subject:")) { continue; } /* Find x -- the array entry where this header is to be saved */ if (chflags & CH_REORDER) { struct ListNode *np = NULL; x = 0; int match = -1; size_t match_len = 0; STAILQ_FOREACH(np, &HeaderOrderList, entries) { size_t hdr_order_len = mutt_str_len(np->data); if (mutt_istrn_equal(buf, np->data, hdr_order_len)) { if ((match == -1) || (hdr_order_len > match_len)) { match = x; match_len = hdr_order_len; } mutt_debug(LL_DEBUG2, "Reorder: %s matches %s", np->data, buf); } x++; } if (match != -1) x = match; } ignore = false; } /* If beginning of header */ if (!ignore) { mutt_debug(LL_DEBUG2, "Reorder: x = %d; hdr_count = %d\n", x, hdr_count); if (this_one) { size_t blen = mutt_str_len(buf); mutt_mem_realloc(&this_one, this_one_len + blen + 1); mutt_strn_copy(this_one + this_one_len, buf, blen, blen + 1); this_one_len += blen; } else { this_one = mutt_str_dup(buf); this_one_len = mutt_str_len(this_one); } } } /* while (ftello (fp_in) < off_end) */ /* Do we have anything pending? -- XXX, same code as in above in the loop. */ if (this_one) { if (chflags & CH_DECODE) { if (address_header_decode(&this_one) == 0) rfc2047_decode(&this_one); this_one_len = mutt_str_len(this_one); } add_one_header(&headers, x, this_one); this_one = NULL; } /* Now output the headers in order */ bool error = false; char **hp = NULL; const short c_wrap = cs_subset_number(NeoMutt->sub, "wrap"); ARRAY_FOREACH(hp, &headers) { if (!error && hp && *hp) { /* We couldn't do the prefixing when reading because RFC2047 * decoding may have concatenated lines. */ if (chflags & (CH_DECODE | CH_PREFIX)) { const char *pre = (chflags & CH_PREFIX) ? prefix : NULL; wraplen = mutt_window_wrap_cols(wraplen, c_wrap); if (mutt_write_one_header(fp_out, 0, *hp, pre, wraplen, chflags, NeoMutt->sub) == -1) { error = true; } } else { if (fputs(*hp, fp_out) == EOF) { error = true; } } } FREE(hp); } ARRAY_FREE(&headers); if (error) return -1; return 0; } /** * mutt_copy_header - Copy Email header * @param fp_in FILE pointer to read from * @param e Email * @param fp_out FILE pointer to write to * @param chflags See #CopyHeaderFlags * @param prefix Prefix for quoting headers (if #CH_PREFIX is set) * @param wraplen Width to wrap at (when chflags & CH_DISPLAY) * @retval 0 Success * @retval -1 Failure */ int mutt_copy_header(FILE *fp_in, struct Email *e, FILE *fp_out, CopyHeaderFlags chflags, const char *prefix, int wraplen) { char *temp_hdr = NULL; if (e->env) { chflags |= ((e->env->changed & MUTT_ENV_CHANGED_IRT) ? CH_UPDATE_IRT : 0) | ((e->env->changed & MUTT_ENV_CHANGED_REFS) ? CH_UPDATE_REFS : 0) | ((e->env->changed & MUTT_ENV_CHANGED_XLABEL) ? CH_UPDATE_LABEL : 0) | ((e->env->changed & MUTT_ENV_CHANGED_SUBJECT) ? CH_UPDATE_SUBJECT : 0); } if (mutt_copy_hdr(fp_in, fp_out, e->offset, e->body->offset, chflags, prefix, wraplen) == -1) return -1; if (chflags & CH_TXTPLAIN) { char chsbuf[128] = { 0 }; char buf[128] = { 0 }; fputs("MIME-Version: 1.0\n", fp_out); fputs("Content-Transfer-Encoding: 8bit\n", fp_out); fputs("Content-Type: text/plain; charset=", fp_out); const char *const c_charset = cc_charset(); mutt_ch_canonical_charset(chsbuf, sizeof(chsbuf), c_charset ? c_charset : "us-ascii"); mutt_addr_cat(buf, sizeof(buf), chsbuf, MimeSpecials); fputs(buf, fp_out); fputc('\n', fp_out); } if ((chflags & CH_UPDATE_IRT) && !STAILQ_EMPTY(&e->env->in_reply_to)) { fputs("In-Reply-To:", fp_out); struct ListNode *np = NULL; STAILQ_FOREACH(np, &e->env->in_reply_to, entries) { fputc(' ', fp_out); fputs(np->data, fp_out); } fputc('\n', fp_out); } if ((chflags & CH_UPDATE_REFS) && !STAILQ_EMPTY(&e->env->references)) { fputs("References:", fp_out); mutt_write_references(&e->env->references, fp_out, 0); fputc('\n', fp_out); } if ((chflags & CH_UPDATE) && ((chflags & CH_NOSTATUS) == 0)) { if (e->old || e->read) { fputs("Status: ", fp_out); if (e->read) fputs("RO", fp_out); else if (e->old) fputc('O', fp_out); fputc('\n', fp_out); } if (e->flagged || e->replied) { fputs("X-Status: ", fp_out); if (e->replied) fputc('A', fp_out); if (e->flagged) fputc('F', fp_out); fputc('\n', fp_out); } } if (chflags & CH_UPDATE_LEN && ((chflags & CH_NOLEN) == 0)) { fprintf(fp_out, "Content-Length: " OFF_T_FMT "\n", e->body->length); if ((e->lines != 0) || (e->body->length == 0)) fprintf(fp_out, "Lines: %d\n", e->lines); } const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed"); #ifdef USE_NOTMUCH if (chflags & CH_VIRTUAL) { /* Add some fake headers based on notmuch data */ char *folder = nm_email_get_folder(e); if (folder && !(c_weed && mutt_matches_ignore("folder"))) { char buf[1024] = { 0 }; mutt_str_copy(buf, folder, sizeof(buf)); mutt_pretty_mailbox(buf, sizeof(buf)); fputs("Folder: ", fp_out); fputs(buf, fp_out); fputc('\n', fp_out); } } #endif char *tags = driver_tags_get(&e->tags); if (tags && !(c_weed && mutt_matches_ignore("tags"))) { fputs("Tags: ", fp_out); fputs(tags, fp_out); fputc('\n', fp_out); } FREE(&tags); const struct Slist *const c_send_charset = cs_subset_slist(NeoMutt->sub, "send_charset"); const short c_wrap = cs_subset_number(NeoMutt->sub, "wrap"); if ((chflags & CH_UPDATE_LABEL) && e->env->x_label) { temp_hdr = e->env->x_label; /* env->x_label isn't currently stored with direct references elsewhere. * Mailbox->label_hash strdups the keys. But to be safe, encode a copy */ if (!(chflags & CH_DECODE)) { temp_hdr = mutt_str_dup(temp_hdr); rfc2047_encode(&temp_hdr, NULL, sizeof("X-Label:"), c_send_charset); } if (mutt_write_one_header(fp_out, "X-Label", temp_hdr, (chflags & CH_PREFIX) ? prefix : 0, mutt_window_wrap_cols(wraplen, c_wrap), chflags, NeoMutt->sub) == -1) { return -1; } if (!(chflags & CH_DECODE)) FREE(&temp_hdr); } if ((chflags & CH_UPDATE_SUBJECT) && e->env->subject) { temp_hdr = e->env->subject; /* env->subject is directly referenced in Mailbox->subj_hash, so we * have to be careful not to encode (and thus free) that memory. */ if (!(chflags & CH_DECODE)) { temp_hdr = mutt_str_dup(temp_hdr); rfc2047_encode(&temp_hdr, NULL, sizeof("Subject:"), c_send_charset); } if (mutt_write_one_header(fp_out, "Subject", temp_hdr, (chflags & CH_PREFIX) ? prefix : 0, mutt_window_wrap_cols(wraplen, c_wrap), chflags, NeoMutt->sub) == -1) { return -1; } if (!(chflags & CH_DECODE)) FREE(&temp_hdr); } if ((chflags & CH_NONEWLINE) == 0) { if (chflags & CH_PREFIX) fputs(prefix, fp_out); fputc('\n', fp_out); /* add header terminator */ } if (ferror(fp_out) || feof(fp_out)) return -1; return 0; } /** * count_delete_lines - Count lines to be deleted in this email body * @param fp FILE pointer to read from * @param b Email Body * @param length Number of bytes to be deleted * @param datelen Length of the date * @retval num Number of lines to be deleted * @retval -1 on error * * Count the number of lines and bytes to be deleted in this body */ static int count_delete_lines(FILE *fp, struct Body *b, LOFF_T *length, size_t datelen) { int dellines = 0; if (b->deleted) { if (!mutt_file_seek(fp, b->offset, SEEK_SET)) { return -1; } for (long l = b->length; l; l--) { const int ch = getc(fp); if (ch == EOF) break; if (ch == '\n') dellines++; } /* 3 and 89 come from the added header of three lines in * copy_delete_attach(). 89 is the size of the header(including * the newlines, tabs, and a single digit length), not including * the date length. */ dellines -= 3; *length -= b->length - (89 + datelen); /* Count the number of digits exceeding the first one to write the size */ for (long l = 10; b->length >= l; l *= 10) (*length)++; } else { for (b = b->parts; b; b = b->next) { const int del = count_delete_lines(fp, b, length, datelen); if (del == -1) { return -1; } dellines += del; } } return dellines; } /** * mutt_copy_message_fp - Make a copy of a message from a FILE pointer * @param fp_out Where to write output * @param fp_in Where to get input * @param e Email being copied * @param cmflags Flags, see #CopyMessageFlags * @param chflags Flags, see #CopyHeaderFlags * @param wraplen Width to wrap at (when chflags & CH_DISPLAY) * @retval 0 Success * @retval -1 Failure */ int mutt_copy_message_fp(FILE *fp_out, FILE *fp_in, struct Email *e, CopyMessageFlags cmflags, CopyHeaderFlags chflags, int wraplen) { struct Body *body = e->body; char prefix[128] = { 0 }; LOFF_T new_offset = -1; int rc = 0; if (cmflags & MUTT_CM_PREFIX) { const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed"); if (c_text_flowed) { mutt_str_copy(prefix, ">", sizeof(prefix)); } else { const char *const c_attribution_locale = cs_subset_string(NeoMutt->sub, "attribution_locale"); const char *const c_indent_string = cs_subset_string(NeoMutt->sub, "indent_string"); struct Mailbox *m_cur = get_current_mailbox(); setlocale(LC_TIME, NONULL(c_attribution_locale)); mutt_make_string(prefix, sizeof(prefix), wraplen, NONULL(c_indent_string), m_cur, -1, e, MUTT_FORMAT_NO_FLAGS, NULL); setlocale(LC_TIME, ""); } } if ((cmflags & MUTT_CM_NOHEADER) == 0) { if (cmflags & MUTT_CM_PREFIX) { chflags |= CH_PREFIX; } else if (e->attach_del && (chflags & CH_UPDATE_LEN)) { int new_lines; int rc_attach_del = -1; LOFF_T new_length = body->length; struct Buffer *quoted_date = NULL; quoted_date = buf_pool_get(); buf_addch(quoted_date, '"'); mutt_date_make_date(quoted_date, cs_subset_bool(NeoMutt->sub, "local_date_header")); buf_addch(quoted_date, '"'); /* Count the number of lines and bytes to be deleted */ if (!mutt_file_seek(fp_in, body->offset, SEEK_SET)) { goto attach_del_cleanup; } const int del = count_delete_lines(fp_in, body, &new_length, buf_len(quoted_date)); if (del == -1) { goto attach_del_cleanup; } new_lines = e->lines - del; /* Copy the headers */ if (mutt_copy_header(fp_in, e, fp_out, chflags | CH_NOLEN | CH_NONEWLINE, NULL, wraplen)) goto attach_del_cleanup; fprintf(fp_out, "Content-Length: " OFF_T_FMT "\n", new_length); if (new_lines <= 0) new_lines = 0; else fprintf(fp_out, "Lines: %d\n", new_lines); putc('\n', fp_out); if (ferror(fp_out) || feof(fp_out)) goto attach_del_cleanup; new_offset = ftello(fp_out); /* Copy the body */ if (!mutt_file_seek(fp_in, body->offset, SEEK_SET)) goto attach_del_cleanup; if (copy_delete_attach(body, fp_in, fp_out, buf_string(quoted_date))) goto attach_del_cleanup; buf_pool_release("ed_date); LOFF_T fail = ((ftello(fp_out) - new_offset) - new_length); if (fail) { mutt_error(ngettext("The length calculation was wrong by %ld byte", "The length calculation was wrong by %ld bytes", fail), fail); new_length += fail; } /* Update original message if we are sync'ing a mailfolder */ if (cmflags & MUTT_CM_UPDATE) { e->attach_del = false; e->lines = new_lines; body->offset = new_offset; body->length = new_length; mutt_body_free(&body->parts); } rc_attach_del = 0; attach_del_cleanup: buf_pool_release("ed_date); return rc_attach_del; } if (mutt_copy_header(fp_in, e, fp_out, chflags, (chflags & CH_PREFIX) ? prefix : NULL, wraplen) == -1) { return -1; } new_offset = ftello(fp_out); } if (cmflags & MUTT_CM_DECODE) { /* now make a text/plain version of the message */ struct State state = { 0 }; state.fp_in = fp_in; state.fp_out = fp_out; if (cmflags & MUTT_CM_PREFIX) state.prefix = prefix; if (cmflags & MUTT_CM_DISPLAY) { state.flags |= STATE_DISPLAY; state.wraplen = wraplen; const char *const c_pager = pager_get_pager(NeoMutt->sub); if (!c_pager) state.flags |= STATE_PAGER; } if (cmflags & MUTT_CM_PRINTING) state.flags |= STATE_PRINTING; if (cmflags & MUTT_CM_WEED) state.flags |= STATE_WEED; if (cmflags & MUTT_CM_CHARCONV) state.flags |= STATE_CHARCONV; if (cmflags & MUTT_CM_REPLYING) state.flags |= STATE_REPLYING; if ((WithCrypto != 0) && cmflags & MUTT_CM_VERIFY) state.flags |= STATE_VERIFY; rc = mutt_body_handler(body, &state); } else if ((WithCrypto != 0) && (cmflags & MUTT_CM_DECODE_CRYPT) && (e->security & SEC_ENCRYPT)) { struct Body *cur = NULL; FILE *fp = NULL; if (((WithCrypto & APPLICATION_PGP) != 0) && (cmflags & MUTT_CM_DECODE_PGP) && (e->security & APPLICATION_PGP) && (e->body->type == TYPE_MULTIPART)) { if (crypt_pgp_decrypt_mime(fp_in, &fp, e->body, &cur)) return -1; fputs("MIME-Version: 1.0\n", fp_out); } if (((WithCrypto & APPLICATION_SMIME) != 0) && (cmflags & MUTT_CM_DECODE_SMIME) && (e->security & APPLICATION_SMIME) && (e->body->type == TYPE_APPLICATION)) { if (crypt_smime_decrypt_mime(fp_in, &fp, e->body, &cur)) return -1; } if (!cur) { mutt_error(_("No decryption engine available for message")); return -1; } mutt_write_mime_header(cur, fp_out, NeoMutt->sub); fputc('\n', fp_out); if (!mutt_file_seek(fp, cur->offset, SEEK_SET)) return -1; if (mutt_file_copy_bytes(fp, fp_out, cur->length) == -1) { mutt_file_fclose(&fp); mutt_body_free(&cur); return -1; } mutt_body_free(&cur); mutt_file_fclose(&fp); } else { if (!mutt_file_seek(fp_in, body->offset, SEEK_SET)) return -1; if (cmflags & MUTT_CM_PREFIX) { int c; size_t bytes = body->length; fputs(prefix, fp_out); while (((c = fgetc(fp_in)) != EOF) && bytes--) { fputc(c, fp_out); if (c == '\n') { fputs(prefix, fp_out); } } } else if (mutt_file_copy_bytes(fp_in, fp_out, body->length) == -1) { return -1; } } if ((cmflags & MUTT_CM_UPDATE) && ((cmflags & MUTT_CM_NOHEADER) == 0) && (new_offset != -1)) { body->offset = new_offset; mutt_body_free(&body->parts); } return rc; } /** * mutt_copy_message - Copy a message from a Mailbox * @param fp_out FILE pointer to write to * @param e Email * @param msg Message * @param cmflags Flags, see #CopyMessageFlags * @param chflags Flags, see #CopyHeaderFlags * @param wraplen Width to wrap at (when chflags & CH_DISPLAY) * @retval 0 Success * @retval -1 Failure * * should be made to return -1 on fatal errors, and 1 on non-fatal errors * like partial decode, where it is worth displaying as much as possible */ int mutt_copy_message(FILE *fp_out, struct Email *e, struct Message *msg, CopyMessageFlags cmflags, CopyHeaderFlags chflags, int wraplen) { if (!msg || !e->body) { return -1; } if (fp_out == msg->fp) { mutt_debug(LL_DEBUG1, "trying to read/write from/to the same FILE*!\n"); return -1; } int rc = mutt_copy_message_fp(fp_out, msg->fp, e, cmflags, chflags, wraplen); if ((rc == 0) && (ferror(fp_out) || feof(fp_out))) { mutt_debug(LL_DEBUG1, "failed to detect EOF!\n"); rc = -1; } return rc; } /** * append_message - Appends a copy of the given message to a mailbox * @param dest destination mailbox * @param fp_in where to get input * @param src source mailbox * @param e Email being copied * @param cmflags Flags, see #CopyMessageFlags * @param chflags Flags, see #CopyHeaderFlags * @retval 0 Success * @retval -1 Error */ static int append_message(struct Mailbox *dest, FILE *fp_in, struct Mailbox *src, struct Email *e, CopyMessageFlags cmflags, CopyHeaderFlags chflags) { char buf[256] = { 0 }; struct Message *msg = NULL; int rc; if (!mutt_file_seek(fp_in, e->offset, SEEK_SET)) return -1; if (!fgets(buf, sizeof(buf), fp_in)) return -1; msg = mx_msg_open_new(dest, e, is_from(buf, NULL, 0, NULL) ? MUTT_MSG_NO_FLAGS : MUTT_ADD_FROM); if (!msg) return -1; if ((dest->type == MUTT_MBOX) || (dest->type == MUTT_MMDF)) chflags |= CH_FROM | CH_FORCE_FROM; chflags |= ((dest->type == MUTT_MAILDIR) ? CH_NOSTATUS : CH_UPDATE); rc = mutt_copy_message_fp(msg->fp, fp_in, e, cmflags, chflags, 0); if (mx_msg_commit(dest, msg) != 0) rc = -1; #ifdef USE_NOTMUCH if (msg->committed_path && (dest->type == MUTT_MAILDIR) && (src->type == MUTT_NOTMUCH)) nm_update_filename(src, NULL, msg->committed_path, e); #endif mx_msg_close(dest, &msg); return rc; } /** * mutt_append_message - Append a message * @param m_dst Destination Mailbox * @param m_src Source Mailbox * @param e Email * @param msg Message * @param cmflags Flags, see #CopyMessageFlags * @param chflags Flags, see #CopyHeaderFlags * @retval 0 Success * @retval -1 Failure */ int mutt_append_message(struct Mailbox *m_dst, struct Mailbox *m_src, struct Email *e, struct Message *msg, CopyMessageFlags cmflags, CopyHeaderFlags chflags) { if (!e) return -1; const bool own_msg = !msg; if (own_msg && !(msg = mx_msg_open(m_src, e))) { return -1; } int rc = append_message(m_dst, msg->fp, m_src, e, cmflags, chflags); if (own_msg) { mx_msg_close(m_src, &msg); } return rc; } /** * copy_delete_attach - Copy a message, deleting marked attachments * @param b Email Body * @param fp_in FILE pointer to read from * @param fp_out FILE pointer to write to * @param quoted_date Date stamp * @retval 0 Success * @retval -1 Failure * * This function copies a message body, while deleting _in_the_copy_ * any attachments which are marked for deletion. * Nothing is changed in the original message -- this is left to the caller. */ static int copy_delete_attach(struct Body *b, FILE *fp_in, FILE *fp_out, const char *quoted_date) { struct Body *part = NULL; for (part = b->parts; part; part = part->next) { if (part->deleted || part->parts) { /* Copy till start of this part */ if (mutt_file_copy_bytes(fp_in, fp_out, part->hdr_offset - ftello(fp_in))) { return -1; } if (part->deleted) { /* If this is modified, count_delete_lines() needs to be changed too */ fprintf(fp_out, "Content-Type: message/external-body; access-type=x-mutt-deleted;\n" "\texpiration=%s; length=" OFF_T_FMT "\n" "\n", quoted_date, part->length); if (ferror(fp_out)) { return -1; } /* Copy the original mime headers */ if (mutt_file_copy_bytes(fp_in, fp_out, part->offset - ftello(fp_in))) { return -1; } /* Skip the deleted body */ if (!mutt_file_seek(fp_in, part->offset + part->length, SEEK_SET)) { return -1; } } else { if (copy_delete_attach(part, fp_in, fp_out, quoted_date)) { return -1; } } } } /* Copy the last parts */ if (mutt_file_copy_bytes(fp_in, fp_out, b->offset + b->length - ftello(fp_in))) return -1; return 0; } /** * address_header_decode - Parse an email's headers * @param[out] h Array of header strings * @retval 0 Success * @retval 1 Failure */ static int address_header_decode(char **h) { char *s = *h; size_t l; bool rp = false; switch (tolower((unsigned char) *s)) { case 'b': { if (!(l = mutt_istr_startswith(s, "bcc:"))) return 0; break; } case 'c': { if (!(l = mutt_istr_startswith(s, "cc:"))) return 0; break; } case 'f': { if (!(l = mutt_istr_startswith(s, "from:"))) return 0; break; } case 'm': { if (!(l = mutt_istr_startswith(s, "mail-followup-to:"))) return 0; break; } case 'r': { if ((l = mutt_istr_startswith(s, "return-path:"))) { rp = true; break; } else if ((l = mutt_istr_startswith(s, "reply-to:"))) { break; } return 0; } case 's': { if (!(l = mutt_istr_startswith(s, "sender:"))) return 0; break; } case 't': { if (!(l = mutt_istr_startswith(s, "to:"))) return 0; break; } default: return 0; } struct AddressList al = TAILQ_HEAD_INITIALIZER(al); mutt_addrlist_parse(&al, s + l); if (TAILQ_EMPTY(&al)) return 0; mutt_addrlist_to_local(&al); rfc2047_decode_addrlist(&al); struct Address *a = NULL; TAILQ_FOREACH(a, &al, entries) { if (a->personal) { buf_dequote_comment(a->personal); } } /* angle brackets for return path are mandated by RFC5322, * so leave Return-Path as-is */ if (rp) { *h = mutt_str_dup(s); } else { struct Buffer buf = { 0 }; (*h)[l - 1] = '\0'; mutt_addrlist_write_wrap(&al, &buf, *h); buf_addch(&buf, '\n'); *h = buf.data; } mutt_addrlist_clear(&al); FREE(&s); return 1; }