2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
5 * This file is part of mutt-ng, see http://www.muttng.org/.
6 * It's licensed under the GNU General Public License,
7 * please see the file GPL in the top level source directory.
25 #include <sys/utsname.h>
27 #include <lib-lib/mem.h>
28 #include <lib-lib/ascii.h>
29 #include <lib-lib/str.h>
30 #include <lib-lib/macros.h>
31 #include <lib-lib/file.h>
33 #include <lib-sys/exit.h>
34 #include <lib-sys/mutt_signal.h>
36 #include <lib-mime/mime.h>
38 #include <lib-ui/curses.h>
42 #include "recvattach.h"
47 #include <lib-crypt/crypt.h>
48 #include "mutt_idna.h"
51 # include "mutt_libesmtp.h"
52 #endif /* USE_LIBESMTP */
58 #ifdef HAVE_SYSEXITS_H
60 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
64 /* If you are debugging this file, comment out the following line. */
73 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
75 static void transform_to_7bit (BODY * a, FILE * fpin);
77 static void encode_quoted (fgetconv_t * fc, FILE * fout, int istext)
80 char line[77], savechar;
82 while ((c = fgetconv (fc)) != EOF) {
83 /* Wrap the line if needed. */
84 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
85 /* If the last character is "quoted", then be sure to move all three
86 * characters to the next line. Otherwise, just move the last
89 if (line[linelen - 3] == '=') {
90 line[linelen - 3] = 0;
95 line[1] = line[linelen - 2];
96 line[2] = line[linelen - 1];
100 savechar = line[linelen - 1];
101 line[linelen - 1] = '=';
110 /* Escape lines that begin with/only contain "the message separator". */
111 if (linelen == 4 && !m_strncmp("From", line, 4)) {
112 m_strcpy(line, sizeof(line), "=46rom");
115 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
116 m_strcpy(line, sizeof(line), "=66rom");
119 else if (linelen == 1 && line[0] == '.') {
120 m_strcpy(line, sizeof(line), "=2E");
125 if (c == '\n' && istext) {
126 /* Check to make sure there is no trailing space on this line. */
128 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
130 sprintf (line + linelen - 1, "=%2.2X",
131 (unsigned char) line[linelen - 1]);
135 savechar = line[linelen - 1];
137 line[linelen - 1] = '=';
140 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
150 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
151 /* Check to make sure there is enough room for the quoted character.
152 * If not, wrap to the next line.
155 line[linelen++] = '=';
161 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
165 /* Don't worry about wrapping the line here. That will happen during
166 * the next iteration when I'll also know what the next character is.
172 /* Take care of anything left in the buffer */
174 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
175 /* take care of trailing whitespace */
177 sprintf (line + linelen - 1, "=%2.2X",
178 (unsigned char) line[linelen - 1]);
180 savechar = line[linelen - 1];
181 line[linelen - 1] = '=';
185 sprintf (line, "=%2.2X", (unsigned char) savechar);
194 static char b64_buffer[3];
195 static short b64_num;
196 static short b64_linelen;
198 static void b64_flush (FILE * fout)
205 if (b64_linelen >= 72) {
210 for (i = b64_num; i < 3; i++)
211 b64_buffer[i] = '\0';
213 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
216 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
221 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
225 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
230 while (b64_linelen % 4) {
239 static void b64_putc (char c, FILE * fout)
244 b64_buffer[b64_num++] = c;
248 static void encode_base64 (fgetconv_t * fc, FILE * fout, int istext)
252 b64_num = b64_linelen = 0;
254 while ((ch = fgetconv (fc)) != EOF) {
255 if (istext && ch == '\n' && ch1 != '\r')
256 b64_putc ('\r', fout);
264 static void encode_8bit (fgetconv_t * fc, FILE * fout, int istext)
268 while ((ch = fgetconv (fc)) != EOF)
273 int mutt_write_mime_header (BODY * a, FILE * f)
282 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
287 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
289 for (p = a->parameter; p; p = p->next) {
298 tmp = m_strdup(p->value);
299 encode = rfc2231_encode_string (&tmp);
300 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
302 /* Dirty hack to make messages readable by Outlook Express
303 * for the Mac: force quotes around the boundary parameter
304 * even when they aren't needed.
307 if (!ascii_strcasecmp (p->attribute, "boundary")
308 && !strcmp (buffer, tmp))
309 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
313 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
315 if (len + tmplen + 2 > 76) {
324 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
332 fprintf (f, "Content-Description: %s\n", a->description);
334 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
337 if (!(fn = a->d_filename))
343 /* Strip off the leading path... */
344 if ((t = strrchr (fn, '/')))
351 encode = rfc2231_encode_string (&tmp);
352 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
354 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
360 if (a->encoding != ENC7BIT)
361 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
363 /* Do NOT add the terminator here!!! */
364 return (ferror (f) ? -1 : 0);
367 # define write_as_text_part(a) (mutt_is_text_part(a) || mutt_is_application_pgp(a))
369 int mutt_write_mime_body (BODY * a, FILE * f)
372 char boundary[SHORT_STRING];
373 char send_charset[SHORT_STRING];
378 if (a->type == TYPEMULTIPART) {
379 /* First, find the boundary to use */
380 if (!(p = parameter_getval(a->parameter, "boundary"))) {
381 mutt_error _("No boundary parameter found! [report this error]");
385 m_strcpy(boundary, sizeof(boundary), p);
387 for (t = a->parts; t; t = t->next) {
388 fprintf (f, "\n--%s\n", boundary);
389 if (mutt_write_mime_header (t, f) == -1)
392 if (mutt_write_mime_body (t, f) == -1)
395 fprintf (f, "\n--%s--\n", boundary);
396 return (ferror (f) ? -1 : 0);
399 /* This is pretty gross, but it's the best solution for now... */
400 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
401 fputs ("Version: 1\n", f);
405 if ((fpin = fopen (a->filename, "r")) == NULL) {
406 mutt_error (_("%s no longer exists!"), a->filename);
410 if (a->type == TYPETEXT && (!a->noconv))
411 fc = fgetconv_open (fpin, a->file_charset,
412 mutt_get_body_charset (send_charset,
413 sizeof (send_charset), a), 0);
415 fc = fgetconv_open (fpin, 0, 0, 0);
417 if (a->encoding == ENCQUOTEDPRINTABLE)
418 encode_quoted (fc, f, write_as_text_part (a));
419 else if (a->encoding == ENCBASE64)
420 encode_base64 (fc, f, write_as_text_part (a));
421 else if (a->type == TYPETEXT && (!a->noconv))
422 encode_8bit (fc, f, write_as_text_part (a));
424 mutt_copy_stream (fpin, f);
426 fgetconv_close (&fc);
429 return (ferror (f) ? -1 : 0);
432 #undef write_as_text_part
443 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
447 int whitespace = s->whitespace;
449 int linelen = s->linelen;
450 int was_cr = s->was_cr;
452 if (!d) { /* This signals EOF */
455 if (linelen > info->linemax)
456 info->linemax = linelen;
461 for (; dlen; d++, dlen--) {
474 if (linelen > info->linemax)
475 info->linemax = linelen;
490 if (linelen > info->linemax)
491 info->linemax = linelen;
496 else if (ch == '\r') {
504 else if (ch == '\t' || ch == '\f') {
508 else if (ch < 32 || ch == 127)
512 if ((ch == 'F') || (ch == 'f'))
522 if (linelen == 2 && ch != 'r')
524 else if (linelen == 3 && ch != 'o')
526 else if (linelen == 4) {
539 if (ch != ' ' && ch != '\t')
544 s->whitespace = whitespace;
546 s->linelen = linelen;
552 * Find the best charset conversion of the file from fromcode into one
553 * of the tocodes. If successful, set *tocode and CONTENT *info and
554 * return the number of characters converted inexactly. If no
555 * conversion was possible, return -1.
557 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
558 * which would otherwise prevent us from knowing the number of inexact
559 * conversions. Where the candidate target charset is UTF-8 we avoid
560 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
561 * fails with some libraries.
563 * We assume that the output from iconv is never more than 4 times as
564 * long as the input for any pair of charsets we might be interested
567 static ssize_t convert_file_to (FILE * file, const char *fromcode,
568 int ncodes, const char **tocodes,
569 int *tocode, CONTENT * info)
573 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
576 ssize_t ibl, obl, ubl, ubl1, n, ret;
579 CONTENT_STATE *states;
582 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
583 if (cd1 == MUTT_ICONV_ERROR)
586 cd = p_new(iconv_t, ncodes);
587 score = p_new(ssize_t, ncodes);
588 states = p_new(CONTENT_STATE, ncodes);
589 infos = p_new(CONTENT, ncodes);
591 for (i = 0; i < ncodes; i++)
592 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
593 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
595 /* Special case for conversion to UTF-8 */
596 cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
602 /* Try to fill input buffer */
603 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
606 /* Convert to UTF-8 */
608 ob = bufu, obl = sizeof (bufu);
609 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
610 assert (n == -1 || !n);
611 if (n == -1 && ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
612 assert (errno == EILSEQ ||
613 (errno == EINVAL && ib == bufi && ibl < ssizeof (bufi)));
619 /* Convert from UTF-8 */
620 for (i = 0; i < ncodes; i++)
621 if (cd[i] != MUTT_ICONV_ERROR && score[i] != -1) {
622 ub = bufu, ubl = ubl1;
623 ob = bufo, obl = sizeof (bufo);
624 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
630 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
633 else if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1)
634 /* Special case for conversion to UTF-8 */
635 update_content_info (&infos[i], &states[i], bufu, ubl1);
638 /* Save unused input */
639 memmove (bufi, ib, ibl);
640 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
647 /* Find best score */
649 for (i = 0; i < ncodes; i++) {
650 if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1) {
651 /* Special case for conversion to UTF-8 */
656 else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
658 else if (ret == -1 || score[i] < ret) {
666 memcpy (info, &infos[*tocode], sizeof (CONTENT));
667 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
671 for (i = 0; i < ncodes; i++)
672 if (cd[i] != MUTT_ICONV_ERROR)
684 #endif /* !HAVE_ICONV */
688 * Find the first of the fromcodes that gives a valid conversion and
689 * the best charset conversion of the file into one of the tocodes. If
690 * successful, set *fromcode and *tocode to dynamically allocated
691 * strings, set CONTENT *info, and return the number of characters
692 * converted inexactly. If no conversion was possible, return -1.
694 * Both fromcodes and tocodes may be colon-separated lists of charsets.
695 * However, if fromcode is zero then fromcodes is assumed to be the
696 * name of a single charset even if it contains a colon.
698 static ssize_t convert_file_from_to (FILE * file,
699 const char *fromcodes,
700 const char *tocodes, char **fromcode,
701 char **tocode, CONTENT * info)
709 /* Count the tocodes */
711 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
712 if ((c1 = strchr (c, ':')) == c)
718 tcode = p_new(char *, ncodes);
719 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
720 if ((c1 = strchr (c, ':')) == c)
722 tcode[i] = m_substrdup(c, c1);
727 /* Try each fromcode in turn */
728 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
729 if ((c1 = strchr (c, ':')) == c)
731 fcode = m_substrdup(c, c1);
733 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
745 /* There is only one fromcode */
746 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
755 for (i = 0; i < ncodes; i++)
764 * Analyze the contents of a file to determine which MIME encoding to use.
765 * Also set the body charset, sometimes, or not.
767 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
772 char *fromcode = NULL;
783 if (stat (fname, &sb) == -1) {
784 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
788 if (!S_ISREG (sb.st_mode)) {
789 mutt_error (_("%s isn't a regular file."), fname);
793 if ((fp = fopen (fname, "r")) == NULL) {
797 info = p_new(CONTENT, 1);
800 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
801 const char *chs = parameter_getval(b->parameter, "charset");
802 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
803 FileCharset : Charset) : Charset;
804 if (Charset && (chs || SendCharset) &&
805 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
806 &fromcode, &tocode, info) != -1) {
808 charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
809 parameter_setval(&b->parameter, "charset", chsbuf);
811 b->file_charset = fromcode;
819 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
820 update_content_info (info, &state, buffer, r);
821 update_content_info (info, &state, 0, 0);
825 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
826 parameter_setval(&b->parameter, "charset",
827 (!info->hibin ? "us-ascii"
828 : Charset && !charset_is_us_ascii(Charset) ? Charset : "unknown-8bit"));
833 /* Given a file with path ``s'', see if there is a registered MIME type.
834 * returns the major MIME type, and copies the subtype to ``d''. First look
835 * for ~/.mime.types, then look in a system mime.types if we can find one.
836 * The longest match is used so that we can match `ps.gz' when `gz' also
840 int mutt_lookup_mime_type (BODY * att, const char *path)
844 char buf[LONG_STRING];
845 char subtype[STRING], xtype[STRING];
847 int szf, sze, cur_sze;
855 szf = m_strlen(path);
857 for (count = 0; count < 4; count++) {
859 * can't use strtok() because we use it in an inner loop below, so use
860 * a switch statement here instead.
864 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
867 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
870 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
873 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
876 goto bye; /* shouldn't happen */
879 if ((f = fopen (buf, "r")) != NULL) {
880 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
881 /* weed out any comments */
882 if ((p = strchr (buf, '#')))
885 /* remove any leading space. */
886 ct = vskipspaces(buf);
888 /* position on the next field in this line */
889 if ((p = strpbrk (ct, " \t")) == NULL)
894 /* cycle through the file extensions */
895 while ((p = strtok (p, " \t\n"))) {
897 if ((sze > cur_sze) && (szf >= sze) &&
898 (m_strcasecmp(path + szf - sze, p) == 0
899 || ascii_strcasecmp (path + szf - sze, p) == 0)
900 && (szf == sze || path[szf - sze - 1] == '.'))
902 /* get the content-type */
904 if ((p = strchr (ct, '/')) == NULL) {
905 /* malformed line, just skip it. */
910 for (q = p; *q && !ISSPACE (*q); q++);
912 m_strncpy(subtype, sizeof(subtype), p, q - p);
914 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
915 m_strcpy(xtype, sizeof(xtype), ct);
928 if (type != TYPEOTHER || *xtype != '\0') {
930 m_strreplace(&att->subtype, subtype);
931 m_strreplace(&att->xtype, xtype);
937 void mutt_message_to_7bit (BODY * a, FILE * fp)
939 char temp[_POSIX_PATH_MAX];
945 if (!a->filename && fp)
947 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
948 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
953 if (stat (a->filename, &sb) == -1) {
954 mutt_perror ("stat");
957 a->length = sb.st_size;
961 if (!(fpout = safe_fopen (temp, "w+"))) {
962 mutt_perror ("fopen");
966 fseeko (fpin, a->offset, 0);
967 a->parts = mutt_parse_messageRFC822 (fpin, a);
969 transform_to_7bit (a->parts, fpin);
971 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
972 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
974 fputs ("MIME-Version: 1.0\n", fpout);
975 mutt_write_mime_header (a->parts, fpout);
977 mutt_write_mime_body (a->parts, fpout);
989 a->encoding = ENC7BIT;
990 a->d_filename = a->filename;
991 if (a->filename && a->unlink)
992 unlink (a->filename);
993 a->filename = m_strdup(temp);
995 if (stat (a->filename, &sb) == -1) {
996 mutt_perror ("stat");
999 a->length = sb.st_size;
1000 body_list_wipe(&a->parts);
1001 a->hdr->content = NULL;
1004 static void transform_to_7bit (BODY * a, FILE * fpin)
1006 char buff[_POSIX_PATH_MAX];
1011 for (; a; a = a->next) {
1012 if (a->type == TYPEMULTIPART) {
1013 if (a->encoding != ENC7BIT)
1014 a->encoding = ENC7BIT;
1016 transform_to_7bit (a->parts, fpin);
1018 else if (mutt_is_message_type (a->type, a->subtype)) {
1019 mutt_message_to_7bit (a, fpin);
1023 a->force_charset = 1;
1026 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1027 mutt_perror ("fopen");
1031 mutt_decode_attachment (a, &s);
1033 a->d_filename = a->filename;
1034 a->filename = m_strdup(buff);
1036 if (stat (a->filename, &sb) == -1) {
1037 mutt_perror ("stat");
1040 a->length = sb.st_size;
1042 mutt_update_encoding (a);
1043 if (a->encoding == ENC8BIT)
1044 a->encoding = ENCQUOTEDPRINTABLE;
1045 else if (a->encoding == ENCBINARY)
1046 a->encoding = ENCBASE64;
1051 /* determine which Content-Transfer-Encoding to use */
1052 static void mutt_set_encoding (BODY * b, CONTENT * info)
1054 char send_charset[SHORT_STRING];
1056 if (b->type == TYPETEXT) {
1058 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1059 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1060 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1061 b->encoding = ENCQUOTEDPRINTABLE;
1062 else if (info->hibin)
1063 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1065 b->encoding = ENC7BIT;
1067 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1068 if (info->lobin || info->hibin) {
1069 if (option (OPTALLOW8BIT) && !info->lobin)
1070 b->encoding = ENC8BIT;
1072 mutt_message_to_7bit (b, NULL);
1075 b->encoding = ENC7BIT;
1077 else if (b->type == TYPEAPPLICATION
1078 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1079 b->encoding = ENC7BIT;
1082 /* Determine which encoding is smaller */
1083 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1084 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1085 b->encoding = ENCBASE64;
1087 b->encoding = ENCQUOTEDPRINTABLE;
1091 void mutt_stamp_attachment (BODY * a)
1093 a->stamp = time (NULL);
1096 /* Get a body's character set */
1098 char *mutt_get_body_charset (char *d, ssize_t dlen, BODY * b)
1100 const char *p = NULL;
1102 if (b && b->type != TYPETEXT)
1106 p = parameter_getval(b->parameter, "charset");
1109 charset_canonicalize (d, dlen, p);
1111 m_strcpy(d, dlen, "us-ascii");
1117 /* Assumes called from send mode where BODY->filename points to actual file */
1118 void mutt_update_encoding (BODY * a)
1121 char chsbuff[STRING];
1123 /* override noconv when it's us-ascii */
1124 if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1127 if (!a->force_charset && !a->noconv)
1128 parameter_delval(&a->parameter, "charset");
1130 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1133 mutt_set_encoding (a, info);
1134 mutt_stamp_attachment (a);
1136 p_delete(&a->content);
1141 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1143 char buffer[LONG_STRING];
1146 int cmflags, chflags;
1147 int pgp = hdr->security;
1149 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1150 (hdr->security & ENCRYPT)) {
1151 if (!crypt_valid_passphrase (hdr->security))
1155 mutt_mktemp (buffer);
1156 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1160 body->type = TYPEMESSAGE;
1161 body->subtype = m_strdup("rfc822");
1162 body->filename = m_strdup(buffer);
1165 body->disposition = DISPINLINE;
1168 mutt_parse_mime_message (ctx, hdr);
1173 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1174 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1175 chflags |= CH_MIME | CH_TXTPLAIN;
1176 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1177 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1179 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1180 if (mutt_is_multipart_encrypted (hdr->content)) {
1181 chflags |= CH_MIME | CH_NONEWLINE;
1182 cmflags = M_CM_DECODE_PGP;
1185 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1186 chflags |= CH_MIME | CH_TXTPLAIN;
1187 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1190 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1191 chflags |= CH_MIME | CH_TXTPLAIN;
1192 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1193 pgp &= ~SMIMEENCRYPT;
1197 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1202 body->hdr = header_new();
1203 body->hdr->offset = 0;
1204 /* we don't need the user headers here */
1205 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1206 body->hdr->security = pgp;
1207 mutt_update_encoding (body);
1208 body->parts = body->hdr->content;
1215 BODY *mutt_make_file_attach (const char *path)
1221 att->filename = m_strdup(path);
1223 /* Attempt to determine the appropriate content-type based on the filename
1230 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1231 path)) != TYPEOTHER || *xbuf != '\0') {
1233 att->subtype = m_strdup(buf);
1234 att->xtype = m_strdup(xbuf);
1239 mutt_lookup_mime_type (att, path);
1243 if ((info = mutt_get_content_info (path, att)) == NULL) {
1244 body_list_wipe(&att);
1248 if (!att->subtype) {
1249 if (info->lobin == 0
1250 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1252 * Statistically speaking, there should be more than 10% "lobin"
1253 * chars if this is really a binary file...
1255 att->type = TYPETEXT;
1256 att->subtype = m_strdup("plain");
1259 att->type = TYPEAPPLICATION;
1260 att->subtype = m_strdup("octet-stream");
1264 mutt_update_encoding (att);
1268 static int get_toplevel_encoding (BODY * a)
1272 for (; a; a = a->next) {
1273 if (a->encoding == ENCBINARY)
1275 else if (a->encoding == ENC8BIT)
1282 BODY *mutt_make_multipart (BODY * b)
1287 new->type = TYPEMULTIPART;
1288 new->subtype = m_strdup("mixed");
1289 new->encoding = get_toplevel_encoding (b);
1290 parameter_set_boundary(&new->parameter);
1292 new->disposition = DISPINLINE;
1298 /* remove the multipart body if it exists */
1299 BODY *mutt_remove_multipart (BODY * b)
1312 char *mutt_make_date (char *s, ssize_t len)
1314 time_t t = time (NULL);
1315 struct tm *l = localtime (&t);
1316 time_t tz = mutt_local_tz (t);
1320 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1321 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1322 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1323 (int) tz / 60, (int) abs (tz) % 60);
1327 /* wrapper around mutt_write_address() so we can handle very large
1328 recipient lists without needing a huge temporary buffer in memory */
1329 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1333 char buf[LONG_STRING];
1341 rfc822_write_address (buf, sizeof (buf), adr, display);
1342 len = m_strlen(buf);
1343 if (count && linelen + len > 74) {
1345 linelen = len + 8; /* tab is usually about 8 spaces... */
1348 if (count && adr->mailbox) {
1356 if (!adr->group && adr->next && adr->next->mailbox) {
1366 /* arbitrary number of elements to grow the array by */
1371 /* need to write the list in reverse because they are stored in reverse order
1372 * when parsed to speed up threading
1374 void mutt_write_references (string_list_t * r, FILE * f)
1376 string_list_t **ref = NULL;
1377 int refcnt = 0, refmax = 0;
1379 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1380 if (refcnt == refmax)
1381 p_realloc(&ref, refmax += REF_INC);
1385 while (refcnt-- > 0) {
1387 fputs (ref[refcnt]->data, f);
1393 /* Note: all RFC2047 encoding should be done outside of this routine, except
1394 * for the "real name." This will allow this routine to be used more than
1395 * once, if necessary.
1397 * Likewise, all IDN processing should happen outside of this routine.
1399 * mode == 1 => "lite" mode (used for edit_hdrs)
1400 * mode == 0 => normal mode. write full header + MIME headers
1401 * mode == -1 => write just the envelope info (used for postponing messages)
1403 * privacy != 0 => will omit any headers which may identify the user.
1404 * Output generated is suitable for being sent through
1405 * anonymous remailer chains.
1409 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1410 int mode, int privacy)
1412 char buffer[LONG_STRING];
1414 string_list_t *tmp = env->userhdrs;
1415 int has_agent = 0; /* user defined user-agent header field exists */
1416 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1419 if (!option (OPTNEWSSEND))
1421 if (mode == 0 && !privacy)
1422 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1424 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1426 /* OPTUSEFROM is not consulted here so that we can still write a From:
1427 * field if the user sets it with the `my_hdr' command
1429 if (env->from && !privacy) {
1431 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1432 fprintf (fp, "From: %s\n", buffer);
1437 mutt_write_address_list (env->to, fp, 4, 0);
1441 if (!option (OPTNEWSSEND))
1443 if (EDIT_HEADER("To:"))
1444 fputs ("To:\n", fp);
1448 mutt_write_address_list (env->cc, fp, 4, 0);
1452 if (!option (OPTNEWSSEND))
1454 if (EDIT_HEADER("Cc:"))
1455 fputs ("Cc:\n", fp);
1458 if (mode != 0 || option (OPTWRITEBCC)) {
1459 fputs ("Bcc: ", fp);
1460 mutt_write_address_list (env->bcc, fp, 5, 0);
1465 if (!option (OPTNEWSSEND))
1467 if (EDIT_HEADER("Bcc:"))
1468 fputs ("Bcc:\n", fp);
1471 if (env->newsgroups)
1472 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1473 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1474 fputs ("Newsgroups:\n", fp);
1476 if (env->followup_to)
1477 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1478 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1479 fputs ("Followup-To:\n", fp);
1481 if (env->x_comment_to)
1482 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1483 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1484 EDIT_HEADER("X-Comment-To:"))
1485 fputs ("X-Comment-To:\n", fp);
1489 fprintf (fp, "Subject: %s\n", env->subject);
1490 else if (mode == 1 && EDIT_HEADER("Subject:"))
1491 fputs ("Subject:\n", fp);
1493 /* save message id if the user has set it */
1494 if (env->message_id && !privacy)
1495 fprintf (fp, "Message-ID: %s\n", env->message_id);
1497 if (env->reply_to) {
1498 fputs ("Reply-To: ", fp);
1499 mutt_write_address_list (env->reply_to, fp, 10, 0);
1501 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1502 fputs ("Reply-To:\n", fp);
1504 if (env->mail_followup_to)
1506 if (!option (OPTNEWSSEND))
1509 fputs ("Mail-Followup-To: ", fp);
1510 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1514 if (env->references) {
1515 fputs ("References:", fp);
1516 mutt_write_references (env->references, fp);
1520 /* Add the MIME headers */
1521 fputs ("MIME-Version: 1.0\n", fp);
1522 mutt_write_mime_header (attach, fp);
1525 if (env->in_reply_to) {
1526 fputs ("In-Reply-To:", fp);
1527 mutt_write_references (env->in_reply_to, fp);
1533 /* Add any user defined headers */
1534 for (; tmp; tmp = tmp->next) {
1535 if ((p = strchr (tmp->data, ':'))) {
1536 p = vskipspaces(p + 1);
1538 continue; /* don't emit empty fields. */
1540 /* check to see if the user has overridden the user-agent field */
1541 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1547 fputs (tmp->data, fp);
1552 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1555 if (OperatingSystem != NULL) {
1556 os = OperatingSystem;
1559 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1561 /* Add a vanity header */
1562 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1565 list_del (&hdrs, (list_del_t*)xmemfree);
1567 return (ferror (fp) == 0 ? 0 : -1);
1570 static void encode_headers (string_list_t * h)
1576 for (; h; h = h->next) {
1577 if (!(p = strchr (h->data, ':')))
1581 p = vskipspaces(p + 1);
1587 rfc2047_encode_string (&tmp);
1588 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1590 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1596 const char *mutt_fqdn (short may_hide_host)
1600 if (Fqdn && Fqdn[0] != '@') {
1603 if (may_hide_host && option (OPTHIDDENHOST)) {
1604 if ((p = strchr (Fqdn, '.')))
1607 /* sanity check: don't hide the host if
1608 * the fqdn is something like detebe.org.
1611 if (!p || !(q = strchr (p, '.')))
1619 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1620 static char mutt_normalized_char(char c)
1622 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1625 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1627 #define APPEND_FMT(fmt, arg) \
1629 int snlen = snprintf(buf, len, fmt, arg); \
1634 #define APPEND_BYTE(c) \
1648 static char MsgIdPfx = 'A';
1652 APPEND_BYTE(mutt_normalized_char(c));
1660 APPEND_FMT("%02d", tm->tm_mday);
1663 APPEND_FMT("%02d", tm->tm_hour);
1666 APPEND_FMT("%02d", tm->tm_mon + 1);
1669 APPEND_FMT("%02d", tm->tm_min);
1672 APPEND_FMT("%lo", (unsigned long)now);
1675 APPEND_FMT("%u", (unsigned int)getpid());
1678 APPEND_FMT("%c", MsgIdPfx);
1679 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1682 APPEND_FMT("%u", (unsigned int)rand());
1685 APPEND_FMT("%x", (unsigned int)rand());
1688 APPEND_FMT("%02d", tm->tm_sec);
1691 APPEND_FMT("%u", (unsigned int) now);
1694 APPEND_FMT("%x", (unsigned int) now);
1696 case 'Y': /* this will break in the year 10000 ;-) */
1697 APPEND_FMT("%04d", tm->tm_year + 1900);
1702 default: /* invalid formats are replaced by '.' */
1704 m_strncat(buf, len, ".", 1);
1714 char *mutt_gen_msgid (void)
1716 char buf[SHORT_STRING];
1717 char localpart[SHORT_STRING];
1718 unsigned int localpart_length;
1721 if (!(fqdn = mutt_fqdn (0)))
1722 fqdn = NONULL (Hostname);
1724 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1726 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1728 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1729 return (m_strdup(buf));
1732 static RETSIGTYPE alarm_handler (int sig)
1737 /* invoke sendmail in a subshell
1738 path (in) path to program to execute
1739 args (in) arguments to pass to program
1740 msg (in) temp file containing message to send
1741 tempfile (out) if sendmail is put in the background, this points
1742 to the temporary file containing the stdout of the
1745 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1751 mutt_block_signals_system ();
1754 /* we also don't want to be stopped right now */
1755 sigaddset (&set, SIGTSTP);
1756 sigprocmask (SIG_BLOCK, &set, NULL);
1758 if (SendmailWait >= 0) {
1759 char tmp[_POSIX_PATH_MAX];
1762 *tempfile = m_strdup(tmp);
1765 if ((pid = fork ()) == 0) {
1766 struct sigaction act, oldalrm;
1768 /* save parent's ID before setsid() */
1771 /* we want the delivery to continue even after the main process dies,
1772 * so we put ourselves into another session right away
1776 /* next we close all open files */
1777 #if defined(OPEN_MAX)
1778 for (fd = 0; fd < OPEN_MAX; fd++)
1780 #elif defined(_POSIX_OPEN_MAX)
1781 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1789 /* now the second fork() */
1790 if ((pid = fork ()) == 0) {
1791 /* "msg" will be opened as stdin */
1792 if (open (msg, O_RDONLY, 0) < 0) {
1798 if (SendmailWait >= 0) {
1799 /* *tempfile will be opened as stdout */
1800 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1803 /* redirect stderr to *tempfile too */
1808 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1810 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1814 execv (path, (char**)args);
1817 else if (pid == -1) {
1823 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1824 * SendmailWait = 0: wait forever
1825 * SendmailWait < 0: don't wait
1827 if (SendmailWait > 0) {
1829 act.sa_handler = alarm_handler;
1831 /* need to make sure waitpid() is interrupted on SIGALRM */
1832 act.sa_flags = SA_INTERRUPT;
1836 sigemptyset (&act.sa_mask);
1837 sigaction (SIGALRM, &act, &oldalrm);
1838 alarm (SendmailWait);
1840 else if (SendmailWait < 0)
1841 _exit (0xff & EX_OK);
1843 if (waitpid (pid, &st, 0) > 0) {
1844 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1845 if (SendmailWait && st == (0xff & EX_OK)) {
1846 unlink (*tempfile); /* no longer needed */
1851 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1852 if (SendmailWait > 0) {
1858 /* reset alarm; not really needed, but... */
1860 sigaction (SIGALRM, &oldalrm, NULL);
1862 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1863 /* the parent is already dead */
1871 sigprocmask (SIG_UNBLOCK, &set, NULL);
1873 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1874 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1876 st = S_ERR; /* error */
1878 mutt_unblock_signals_system (1);
1883 static const char **
1884 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1886 for (; addr; addr = addr->next) {
1887 /* weed out group mailboxes, since those are for display only */
1888 if (addr->mailbox && !addr->group) {
1889 if (*argslen == *argsmax)
1890 p_realloc(&args, *argsmax += 5);
1891 args[(*argslen)++] = addr->mailbox;
1897 static const char **
1898 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1900 if (*argslen == *argsmax) {
1901 p_realloc(&args, *argsmax += 5);
1903 args[(*argslen)++] = s;
1907 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1908 address_t * to, address_t * cc, address_t * bcc, /* recips */
1909 const char *msg, /* file containing message */
1911 { /* message contains 8bit chars */
1912 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1913 const char **args = NULL;
1914 ssize_t argslen = 0, argsmax = 0;
1918 if (option (OPTNEWSSEND)) {
1919 char cmd[LONG_STRING];
1921 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1924 i = nntp_post (msg);
1933 s = m_strdup(Sendmail);
1937 while ((ps = strtok (ps, " "))) {
1938 if (argslen == argsmax)
1939 p_realloc(&args, argsmax += 5);
1942 args[argslen++] = ps;
1944 path = m_strdup(ps);
1945 ps = strrchr (ps, '/');
1950 args[argslen++] = ps;
1957 if (!option (OPTNEWSSEND)) {
1959 if (eightbit && option (OPTUSE8BITMIME))
1960 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1962 if (option (OPTENVFROM)) {
1963 address_t *f = NULL;
1966 else if (from && !from->next)
1969 args = add_option (args, &argslen, &argsmax, "-f");
1970 args = add_args (args, &argslen, &argsmax, f);
1974 args = add_option (args, &argslen, &argsmax, "-N");
1975 args = add_option (args, &argslen, &argsmax, DsnNotify);
1978 args = add_option (args, &argslen, &argsmax, "-R");
1979 args = add_option (args, &argslen, &argsmax, DsnReturn);
1981 args = add_option (args, &argslen, &argsmax, "--");
1982 args = add_args (args, &argslen, &argsmax, to);
1983 args = add_args (args, &argslen, &argsmax, cc);
1984 args = add_args (args, &argslen, &argsmax, bcc);
1989 if (argslen == argsmax)
1990 p_realloc(&args, ++argsmax);
1992 args[argslen++] = NULL;
1994 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
1996 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2001 if (stat (childout, &st) == 0 && st.st_size > 0)
2002 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2010 p_delete(&childout);
2015 if (i == (EX_OK & 0xff))
2017 else if (i == S_BKG)
2024 int mutt_invoke_mta (address_t * from, /* the sender */
2025 address_t * to, address_t * cc, address_t * bcc, /* recips */
2026 const char *msg, /* file containing message */
2028 { /* message contains 8bit chars */
2031 if (!option (OPTNEWSSEND))
2034 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2037 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2040 /* For postponing (!final) do the necessary encodings only */
2041 void mutt_prepare_envelope (ENVELOPE * env, int final)
2043 char buffer[LONG_STRING];
2046 if (env->bcc && !(env->to || env->cc)) {
2047 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2048 * recipients if there is no To: or Cc: field, so attempt to suppress
2049 * it by using an empty To: field.
2051 env->to = address_new ();
2053 env->to->next = address_new ();
2056 rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
2059 env->to->mailbox = m_strdup(buffer);
2062 mutt_set_followup_to (env);
2064 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2065 env->message_id = mutt_gen_msgid ();
2068 /* Take care of 8-bit => 7-bit conversion. */
2069 rfc2047_encode_adrlist (env->to, "To");
2070 rfc2047_encode_adrlist (env->cc, "Cc");
2071 rfc2047_encode_adrlist (env->bcc, "Bcc");
2072 rfc2047_encode_adrlist (env->from, "From");
2073 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2074 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2078 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2081 rfc2047_encode_string (&env->subject);
2083 encode_headers (env->userhdrs);
2086 void mutt_unprepare_envelope (ENVELOPE * env)
2088 string_list_t *item;
2090 for (item = env->userhdrs; item; item = item->next)
2091 rfc2047_decode (&item->data);
2093 address_list_wipe(&env->mail_followup_to);
2095 /* back conversions */
2096 rfc2047_decode_adrlist (env->to);
2097 rfc2047_decode_adrlist (env->cc);
2098 rfc2047_decode_adrlist (env->bcc);
2099 rfc2047_decode_adrlist (env->from);
2100 rfc2047_decode_adrlist (env->reply_to);
2101 rfc2047_decode (&env->subject);
2104 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2105 const char *resent_from, address_t * env_from)
2109 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2110 MESSAGE *msg = NULL;
2113 /* Try to bounce each message out, aborting if we get any failures. */
2114 for (i = 0; i < Context->msgcount; i++)
2115 if (Context->hdrs[i]->tagged)
2117 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2122 /* If we failed to open a message, return with error */
2123 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2129 mutt_mktemp (tempfile);
2130 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2131 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2133 if (!option (OPTBOUNCEDELIVERED))
2134 ch_flags |= CH_WEED_DELIVERED;
2136 fseeko (fp, h->offset, 0);
2137 fprintf (f, "Resent-From: %s", resent_from);
2138 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2139 if (MsgIdFormat && *MsgIdFormat)
2140 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2141 fputs ("Resent-To: ", f);
2142 mutt_write_address_list (to, f, 11, 0);
2143 mutt_copy_header (fp, h, f, ch_flags, NULL);
2145 mutt_copy_bytes (fp, f, h->content->length);
2148 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2149 h->content->encoding == ENC8BIT);
2153 mx_close_message (&msg);
2158 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2161 const char *fqdn = mutt_fqdn (1);
2162 char resent_from[STRING];
2166 resent_from[0] = '\0';
2167 from = mutt_default_from ();
2170 rfc822_qualify (from, fqdn);
2172 rfc2047_encode_adrlist (from, "Resent-From");
2173 if (mutt_addrlist_to_idna (from, &err)) {
2174 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2177 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2180 unset_option (OPTNEWSSEND);
2183 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2185 address_list_wipe(&from);
2191 /* given a list of addresses, return a list of unique addresses */
2192 address_t *mutt_remove_duplicates (address_t * addr)
2194 address_t *top = addr;
2195 address_t **last = ⊤
2200 for (tmp = top; tmp && tmp != addr; tmp = tmp->next) {
2201 if (tmp->mailbox && addr->mailbox &&
2202 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2212 address_list_wipe(&addr);
2225 static void set_noconv_flags (BODY * b, short flag)
2227 for (; b; b = b->next) {
2228 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2229 set_noconv_flags (b->parts, flag);
2230 else if (b->type == TYPETEXT && b->noconv) {
2231 parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2236 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2237 int post, char *fcc)
2241 char tempfile[_POSIX_PATH_MAX];
2242 FILE *tempfp = NULL;
2246 set_noconv_flags (hdr->content, 1);
2248 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2252 /* We need to add a Content-Length field to avoid problems where a line in
2253 * the message body begins with "From "
2255 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2256 mutt_mktemp (tempfile);
2257 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2258 mutt_perror (tempfile);
2259 mx_close_mailbox (&f, NULL);
2264 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2265 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2266 mx_close_mailbox (&f, NULL);
2270 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2271 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2273 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2276 /* (postponment) if this was a reply of some sort, <msgid> contians the
2277 * Message-ID: of message replied to. Save it using a special X-Mutt-
2278 * header so it can be picked up if the message is recalled at a later
2279 * point in time. This will allow the message to be marked as replied if
2280 * the same mailbox is still open.
2283 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2285 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2286 * it can be picked up when the message is recalled
2289 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2290 fprintf (msg->fp, "Status: RO\n");
2294 /* (postponment) if the mail is to be signed or encrypted, save this info */
2295 if (post && (hdr->security & APPLICATION_PGP)) {
2296 fputs ("X-Mutt-PGP: ", msg->fp);
2297 if (hdr->security & ENCRYPT)
2298 fputc ('E', msg->fp);
2299 if (hdr->security & SIGN) {
2300 fputc ('S', msg->fp);
2301 if (PgpSignAs && *PgpSignAs)
2302 fprintf (msg->fp, "<%s>", PgpSignAs);
2304 if (hdr->security & INLINE)
2305 fputc ('I', msg->fp);
2306 fputc ('\n', msg->fp);
2309 /* (postponment) if the mail is to be signed or encrypted, save this info */
2310 if (post && (hdr->security & APPLICATION_SMIME)) {
2311 fputs ("X-Mutt-SMIME: ", msg->fp);
2312 if (hdr->security & ENCRYPT) {
2313 fputc ('E', msg->fp);
2314 if (SmimeCryptAlg && *SmimeCryptAlg)
2315 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2317 if (hdr->security & SIGN) {
2318 fputc ('S', msg->fp);
2319 if (SmimeDefaultKey && *SmimeDefaultKey)
2320 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2322 if (hdr->security & INLINE)
2323 fputc ('I', msg->fp);
2324 fputc ('\n', msg->fp);
2328 /* (postponement) if the mail is to be sent through a mixmaster
2329 * chain, save that information
2332 if (post && hdr->chain && hdr->chain) {
2335 fputs ("X-Mutt-Mix:", msg->fp);
2336 for (p = hdr->chain; p; p = p->next)
2337 fprintf (msg->fp, " %s", (char *) p->data);
2339 fputc ('\n', msg->fp);
2344 char sasha[LONG_STRING];
2347 mutt_write_mime_body (hdr->content, tempfp);
2349 /* make sure the last line ends with a newline. Emacs doesn't ensure
2350 * this will happen, and it can cause problems parsing the mailbox
2353 fseeko (tempfp, -1, 2);
2354 if (fgetc (tempfp) != '\n') {
2355 fseeko (tempfp, 0, 2);
2356 fputc ('\n', tempfp);
2360 if (ferror (tempfp)) {
2363 mx_commit_message (msg, &f); /* XXX - really? */
2364 mx_close_message (&msg);
2365 mx_close_mailbox (&f, NULL);
2369 /* count the number of lines */
2371 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2373 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2374 fprintf (msg->fp, "Lines: %d\n\n", lines);
2376 /* copy the body and clean up */
2378 r = mutt_copy_stream (tempfp, msg->fp);
2379 if (fclose (tempfp) != 0)
2381 /* if there was an error, leave the temp version */
2386 fputc ('\n', msg->fp); /* finish off the header */
2387 r = mutt_write_mime_body (hdr->content, msg->fp);
2390 if (mx_commit_message (msg, &f) != 0)
2392 mx_close_message (&msg);
2393 mx_close_mailbox (&f, NULL);
2396 set_noconv_flags (hdr->content, 0);