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.
16 #include <lib-lib/mem.h>
17 #include <lib-lib/ascii.h>
18 #include <lib-lib/str.h>
19 #include <lib-lib/macros.h>
20 #include <lib-lib/file.h>
22 #include <lib-mime/mime.h>
26 #include "recvattach.h"
27 #include "mutt_curses.h"
32 #include "mutt_crypt.h"
33 #include "mutt_idna.h"
35 #include "lib/debug.h"
46 #include <sys/utsname.h>
49 # include "mutt_libesmtp.h"
50 #endif /* USE_LIBESMTP */
56 #ifdef HAVE_SYSEXITS_H
58 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
62 /* If you are debugging this file, comment out the following line. */
71 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
73 static char MsgIdPfx = 'A';
75 static void transform_to_7bit (BODY * a, FILE * fpin);
77 static void encode_quoted (FGETCONV * 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 int 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 * 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 * fc, FILE * fout, int istext)
268 while ((ch = fgetconv (fc)) != EOF)
273 int mutt_write_mime_header (BODY * a, FILE * f)
283 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
286 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
288 for (p = a->parameter; p; p = p->next) {
297 tmp = m_strdup(p->value);
298 encode = rfc2231_encode_string (&tmp);
299 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
301 /* Dirty hack to make messages readable by Outlook Express
302 * for the Mac: force quotes around the boundary parameter
303 * even when they aren't needed.
306 if (!ascii_strcasecmp (p->attribute, "boundary")
307 && !strcmp (buffer, tmp))
308 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
312 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
314 if (len + tmplen + 2 > 76) {
323 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
331 fprintf (f, "Content-Description: %s\n", a->description);
333 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
336 if (!(fn = a->d_filename))
342 /* Strip off the leading path... */
343 if ((t = strrchr (fn, '/')))
350 encode = rfc2231_encode_string (&tmp);
351 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
353 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
359 if (a->encoding != ENC7BIT)
360 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
362 /* Do NOT add the terminator here!!! */
363 return (ferror (f) ? -1 : 0);
366 # define write_as_text_part(a) (mutt_is_text_part(a) \
367 || ((WithCrypto & APPLICATION_PGP)\
368 && mutt_is_application_pgp(a)))
370 int mutt_write_mime_body (BODY * a, FILE * f)
372 char *p, boundary[SHORT_STRING];
373 char send_charset[SHORT_STRING];
378 if (a->type == TYPEMULTIPART) {
379 /* First, find the boundary to use */
380 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
381 debug_print (1, ("no boundary parameter found!\n"));
382 mutt_error _("No boundary parameter found! [report this error]");
386 m_strcpy(boundary, sizeof(boundary), p);
388 for (t = a->parts; t; t = t->next) {
389 fprintf (f, "\n--%s\n", boundary);
390 if (mutt_write_mime_header (t, f) == -1)
393 if (mutt_write_mime_body (t, f) == -1)
396 fprintf (f, "\n--%s--\n", boundary);
397 return (ferror (f) ? -1 : 0);
400 /* This is pretty gross, but it's the best solution for now... */
401 if ((WithCrypto & APPLICATION_PGP)
402 && a->type == TYPEAPPLICATION
403 && m_strcmp(a->subtype, "pgp-encrypted") == 0) {
404 fputs ("Version: 1\n", f);
408 if ((fpin = fopen (a->filename, "r")) == NULL) {
409 debug_print (1, ("%s no longer exists!\n", a->filename));
410 mutt_error (_("%s no longer exists!"), a->filename);
414 if (a->type == TYPETEXT && (!a->noconv))
415 fc = fgetconv_open (fpin, a->file_charset,
416 mutt_get_body_charset (send_charset,
417 sizeof (send_charset), a), 0);
419 fc = fgetconv_open (fpin, 0, 0, 0);
421 if (a->encoding == ENCQUOTEDPRINTABLE)
422 encode_quoted (fc, f, write_as_text_part (a));
423 else if (a->encoding == ENCBASE64)
424 encode_base64 (fc, f, write_as_text_part (a));
425 else if (a->type == TYPETEXT && (!a->noconv))
426 encode_8bit (fc, f, write_as_text_part (a));
428 mutt_copy_stream (fpin, f);
430 fgetconv_close (&fc);
433 return (ferror (f) ? -1 : 0);
436 #undef write_as_text_part
438 #define BOUNDARYLEN 16
439 void mutt_generate_boundary (PARAMETER ** parm)
441 char rs[BOUNDARYLEN + 1];
446 for (i = 0; i < BOUNDARYLEN; i++)
447 *p++ = __m_b64chars[LRAND() % sizeof(__m_b64chars)];
450 mutt_set_parameter ("boundary", rs, parm);
462 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
466 int whitespace = s->whitespace;
468 int linelen = s->linelen;
469 int was_cr = s->was_cr;
471 if (!d) { /* This signals EOF */
474 if (linelen > info->linemax)
475 info->linemax = linelen;
480 for (; dlen; d++, dlen--) {
493 if (linelen > info->linemax)
494 info->linemax = linelen;
509 if (linelen > info->linemax)
510 info->linemax = linelen;
515 else if (ch == '\r') {
523 else if (ch == '\t' || ch == '\f') {
527 else if (ch < 32 || ch == 127)
531 if ((ch == 'F') || (ch == 'f'))
541 if (linelen == 2 && ch != 'r')
543 else if (linelen == 3 && ch != 'o')
545 else if (linelen == 4) {
558 if (ch != ' ' && ch != '\t')
563 s->whitespace = whitespace;
565 s->linelen = linelen;
570 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
571 #define BUGGY_ICONV 1
574 * Find the best charset conversion of the file from fromcode into one
575 * of the tocodes. If successful, set *tocode and CONTENT *info and
576 * return the number of characters converted inexactly. If no
577 * conversion was possible, return -1.
579 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
580 * which would otherwise prevent us from knowing the number of inexact
581 * conversions. Where the candidate target charset is UTF-8 we avoid
582 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
583 * fails with some libraries.
585 * We assume that the output from iconv is never more than 4 times as
586 * long as the input for any pair of charsets we might be interested
589 static size_t convert_file_to (FILE * file, const char *fromcode,
590 int ncodes, const char **tocodes,
591 int *tocode, CONTENT * info)
595 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
598 size_t ibl, obl, ubl, ubl1, n, ret;
601 CONTENT_STATE *states;
604 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
605 if (cd1 == (iconv_t) (-1))
608 cd = p_new(iconv_t, ncodes);
609 score = p_new(size_t, ncodes);
610 states = p_new(CONTENT_STATE, ncodes);
611 infos = p_new(CONTENT, ncodes);
613 for (i = 0; i < ncodes; i++)
614 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
615 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
617 /* Special case for conversion to UTF-8 */
618 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
624 /* Try to fill input buffer */
625 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
628 /* Convert to UTF-8 */
630 ob = bufu, obl = sizeof (bufu);
631 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
632 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
633 if (n == (size_t) (-1) &&
634 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
635 assert (errno == EILSEQ ||
636 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
642 /* Convert from UTF-8 */
643 for (i = 0; i < ncodes; i++)
644 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
645 ub = bufu, ubl = ubl1;
646 ob = bufo, obl = sizeof (bufo);
647 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
648 if (n == (size_t) (-1)) {
649 assert (errno == E2BIG ||
650 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
651 score[i] = (size_t) (-1);
655 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
658 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
659 /* Special case for conversion to UTF-8 */
660 update_content_info (&infos[i], &states[i], bufu, ubl1);
663 /* Save unused input */
664 memmove (bufi, ib, ibl);
665 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
672 /* Find best score */
674 for (i = 0; i < ncodes; i++) {
675 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
676 /* Special case for conversion to UTF-8 */
681 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
683 else if (ret == (size_t) (-1) || score[i] < ret) {
690 if (ret != (size_t) (-1)) {
691 memcpy (info, &infos[*tocode], sizeof (CONTENT));
692 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
696 for (i = 0; i < ncodes; i++)
697 if (cd[i] != (iconv_t) (-1))
709 #endif /* !HAVE_ICONV */
713 * Find the first of the fromcodes that gives a valid conversion and
714 * the best charset conversion of the file into one of the tocodes. If
715 * successful, set *fromcode and *tocode to dynamically allocated
716 * strings, set CONTENT *info, and return the number of characters
717 * converted inexactly. If no conversion was possible, return -1.
719 * Both fromcodes and tocodes may be colon-separated lists of charsets.
720 * However, if fromcode is zero then fromcodes is assumed to be the
721 * name of a single charset even if it contains a colon.
723 static size_t convert_file_from_to (FILE * file,
724 const char *fromcodes,
725 const char *tocodes, char **fromcode,
726 char **tocode, CONTENT * info)
734 /* Count the tocodes */
736 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
737 if ((c1 = strchr (c, ':')) == c)
743 tcode = p_new(char *, ncodes);
744 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
745 if ((c1 = strchr (c, ':')) == c)
747 tcode[i] = str_substrdup (c, c1);
752 /* Try each fromcode in turn */
753 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
754 if ((c1 = strchr (c, ':')) == c)
756 fcode = str_substrdup (c, c1);
758 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
760 if (ret != (size_t) (-1)) {
770 /* There is only one fromcode */
771 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
773 if (ret != (size_t) (-1)) {
780 for (i = 0; i < ncodes; i++)
789 * Analyze the contents of a file to determine which MIME encoding to use.
790 * Also set the body charset, sometimes, or not.
792 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
797 char *fromcode = NULL;
808 if (stat (fname, &sb) == -1) {
809 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
813 if (!S_ISREG (sb.st_mode)) {
814 mutt_error (_("%s isn't a regular file."), fname);
818 if ((fp = fopen (fname, "r")) == NULL) {
819 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
823 info = p_new(CONTENT, 1);
826 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
827 char *chs = mutt_get_parameter ("charset", b->parameter);
828 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
829 FileCharset : Charset) : Charset;
830 if (Charset && (chs || SendCharset) &&
831 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
832 &fromcode, &tocode, info) != (size_t) (-1)) {
834 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
835 mutt_set_parameter ("charset", chsbuf, &b->parameter);
837 b->file_charset = fromcode;
845 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
846 update_content_info (info, &state, buffer, r);
847 update_content_info (info, &state, 0, 0);
851 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
852 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
854 && !mutt_is_us_ascii (Charset) ? Charset :
855 "unknown-8bit"), &b->parameter);
860 /* Given a file with path ``s'', see if there is a registered MIME type.
861 * returns the major MIME type, and copies the subtype to ``d''. First look
862 * for ~/.mime.types, then look in a system mime.types if we can find one.
863 * The longest match is used so that we can match `ps.gz' when `gz' also
867 int mutt_lookup_mime_type (BODY * att, const char *path)
871 char buf[LONG_STRING];
872 char subtype[STRING], xtype[STRING];
874 int szf, sze, cur_sze;
882 szf = m_strlen(path);
884 for (count = 0; count < 4; count++) {
886 * can't use strtok() because we use it in an inner loop below, so use
887 * a switch statement here instead.
891 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
894 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/muttng-mime.types");
897 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
900 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
903 debug_print (1, ("Internal error, count = %d.\n", count));
904 goto bye; /* shouldn't happen */
907 if ((f = fopen (buf, "r")) != NULL) {
908 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
909 /* weed out any comments */
910 if ((p = strchr (buf, '#')))
913 /* remove any leading space. */
914 ct = vskipspaces(buf);
916 /* position on the next field in this line */
917 if ((p = strpbrk (ct, " \t")) == NULL)
922 /* cycle through the file extensions */
923 while ((p = strtok (p, " \t\n"))) {
925 if ((sze > cur_sze) && (szf >= sze) &&
926 (m_strcasecmp(path + szf - sze, p) == 0
927 || ascii_strcasecmp (path + szf - sze, p) == 0)
928 && (szf == sze || path[szf - sze - 1] == '.'))
930 /* get the content-type */
932 if ((p = strchr (ct, '/')) == NULL) {
933 /* malformed line, just skip it. */
938 for (q = p; *q && !ISSPACE (*q); q++);
940 str_substrcpy (subtype, p, q, sizeof (subtype));
942 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
943 m_strcpy(xtype, sizeof(xtype), ct);
956 if (type != TYPEOTHER || *xtype != '\0') {
958 m_strreplace(&att->subtype, subtype);
959 m_strreplace(&att->xtype, xtype);
965 void mutt_message_to_7bit (BODY * a, FILE * fp)
967 char temp[_POSIX_PATH_MAX];
973 if (!a->filename && fp)
975 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
976 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
981 if (stat (a->filename, &sb) == -1) {
982 mutt_perror ("stat");
985 a->length = sb.st_size;
989 if (!(fpout = safe_fopen (temp, "w+"))) {
990 mutt_perror ("fopen");
994 fseeko (fpin, a->offset, 0);
995 a->parts = mutt_parse_messageRFC822 (fpin, a);
997 transform_to_7bit (a->parts, fpin);
999 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1000 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1002 fputs ("MIME-Version: 1.0\n", fpout);
1003 mutt_write_mime_header (a->parts, fpout);
1004 fputc ('\n', fpout);
1005 mutt_write_mime_body (a->parts, fpout);
1017 a->encoding = ENC7BIT;
1018 a->d_filename = a->filename;
1019 if (a->filename && a->unlink)
1020 unlink (a->filename);
1021 a->filename = m_strdup(temp);
1023 if (stat (a->filename, &sb) == -1) {
1024 mutt_perror ("stat");
1027 a->length = sb.st_size;
1028 mutt_free_body (&a->parts);
1029 a->hdr->content = NULL;
1032 static void transform_to_7bit (BODY * a, FILE * fpin)
1034 char buff[_POSIX_PATH_MAX];
1039 for (; a; a = a->next) {
1040 if (a->type == TYPEMULTIPART) {
1041 if (a->encoding != ENC7BIT)
1042 a->encoding = ENC7BIT;
1044 transform_to_7bit (a->parts, fpin);
1046 else if (mutt_is_message_type (a->type, a->subtype)) {
1047 mutt_message_to_7bit (a, fpin);
1051 a->force_charset = 1;
1054 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1055 mutt_perror ("fopen");
1059 mutt_decode_attachment (a, &s);
1061 a->d_filename = a->filename;
1062 a->filename = m_strdup(buff);
1064 if (stat (a->filename, &sb) == -1) {
1065 mutt_perror ("stat");
1068 a->length = sb.st_size;
1070 mutt_update_encoding (a);
1071 if (a->encoding == ENC8BIT)
1072 a->encoding = ENCQUOTEDPRINTABLE;
1073 else if (a->encoding == ENCBINARY)
1074 a->encoding = ENCBASE64;
1079 /* determine which Content-Transfer-Encoding to use */
1080 static void mutt_set_encoding (BODY * b, CONTENT * info)
1082 char send_charset[SHORT_STRING];
1084 if (b->type == TYPETEXT) {
1086 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1087 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1088 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1089 b->encoding = ENCQUOTEDPRINTABLE;
1090 else if (info->hibin)
1091 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1093 b->encoding = ENC7BIT;
1095 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1096 if (info->lobin || info->hibin) {
1097 if (option (OPTALLOW8BIT) && !info->lobin)
1098 b->encoding = ENC8BIT;
1100 mutt_message_to_7bit (b, NULL);
1103 b->encoding = ENC7BIT;
1105 else if (b->type == TYPEAPPLICATION
1106 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1107 b->encoding = ENC7BIT;
1110 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1111 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1114 /* Determine which encoding is smaller */
1115 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1116 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1117 b->encoding = ENCBASE64;
1119 b->encoding = ENCQUOTEDPRINTABLE;
1123 b->encoding = ENC7BIT;
1127 void mutt_stamp_attachment (BODY * a)
1129 a->stamp = time (NULL);
1132 /* Get a body's character set */
1134 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1138 if (b && b->type != TYPETEXT)
1142 p = mutt_get_parameter ("charset", b->parameter);
1145 mutt_canonical_charset (d, dlen, NONULL (p));
1147 m_strcpy(d, dlen, "us-ascii");
1153 /* Assumes called from send mode where BODY->filename points to actual file */
1154 void mutt_update_encoding (BODY * a)
1157 char chsbuff[STRING];
1159 /* override noconv when it's us-ascii */
1160 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1163 if (!a->force_charset && !a->noconv)
1164 mutt_delete_parameter ("charset", &a->parameter);
1166 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1169 mutt_set_encoding (a, info);
1170 mutt_stamp_attachment (a);
1172 p_delete(&a->content);
1177 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1179 char buffer[LONG_STRING];
1182 int cmflags, chflags;
1183 int pgp = WithCrypto ? hdr->security : 0;
1186 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1187 (hdr->security & ENCRYPT)) {
1188 if (!crypt_valid_passphrase (hdr->security))
1193 mutt_mktemp (buffer);
1194 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1197 body = mutt_new_body ();
1198 body->type = TYPEMESSAGE;
1199 body->subtype = m_strdup("rfc822");
1200 body->filename = m_strdup(buffer);
1203 body->disposition = DISPINLINE;
1206 mutt_parse_mime_message (ctx, hdr);
1211 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1212 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1213 chflags |= CH_MIME | CH_TXTPLAIN;
1214 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1215 if ((WithCrypto & APPLICATION_PGP))
1217 if ((WithCrypto & APPLICATION_SMIME))
1218 pgp &= ~SMIMEENCRYPT;
1220 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1221 if ((WithCrypto & APPLICATION_PGP)
1222 && mutt_is_multipart_encrypted (hdr->content)) {
1223 chflags |= CH_MIME | CH_NONEWLINE;
1224 cmflags = M_CM_DECODE_PGP;
1227 else if ((WithCrypto & APPLICATION_PGP)
1228 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1229 chflags |= CH_MIME | CH_TXTPLAIN;
1230 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1233 else if ((WithCrypto & APPLICATION_SMIME)
1234 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1235 chflags |= CH_MIME | CH_TXTPLAIN;
1236 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1237 pgp &= ~SMIMEENCRYPT;
1241 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1246 body->hdr = mutt_new_header ();
1247 body->hdr->offset = 0;
1248 /* we don't need the user headers here */
1249 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1251 body->hdr->security = pgp;
1252 mutt_update_encoding (body);
1253 body->parts = body->hdr->content;
1260 BODY *mutt_make_file_attach (const char *path)
1265 att = mutt_new_body ();
1266 att->filename = m_strdup(path);
1268 /* Attempt to determine the appropriate content-type based on the filename
1275 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1276 path)) != TYPEOTHER || *xbuf != '\0') {
1278 att->subtype = m_strdup(buf);
1279 att->xtype = m_strdup(xbuf);
1284 mutt_lookup_mime_type (att, path);
1288 if ((info = mutt_get_content_info (path, att)) == NULL) {
1289 mutt_free_body (&att);
1293 if (!att->subtype) {
1294 if (info->lobin == 0
1295 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1297 * Statistically speaking, there should be more than 10% "lobin"
1298 * chars if this is really a binary file...
1300 att->type = TYPETEXT;
1301 att->subtype = m_strdup("plain");
1304 att->type = TYPEAPPLICATION;
1305 att->subtype = m_strdup("octet-stream");
1309 mutt_update_encoding (att);
1313 static int get_toplevel_encoding (BODY * a)
1317 for (; a; a = a->next) {
1318 if (a->encoding == ENCBINARY)
1320 else if (a->encoding == ENC8BIT)
1327 BODY *mutt_make_multipart (BODY * b)
1331 new = mutt_new_body ();
1332 new->type = TYPEMULTIPART;
1333 new->subtype = m_strdup("mixed");
1334 new->encoding = get_toplevel_encoding (b);
1335 mutt_generate_boundary (&new->parameter);
1337 new->disposition = DISPINLINE;
1343 /* remove the multipart body if it exists */
1344 BODY *mutt_remove_multipart (BODY * b)
1352 mutt_free_body (&t);
1357 char *mutt_make_date (char *s, size_t len)
1359 time_t t = time (NULL);
1360 struct tm *l = localtime (&t);
1361 time_t tz = mutt_local_tz (t);
1365 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1366 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1367 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1368 (int) tz / 60, (int) abs (tz) % 60);
1372 /* wrapper around mutt_write_address() so we can handle very large
1373 recipient lists without needing a huge temporary buffer in memory */
1374 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1378 char buf[LONG_STRING];
1386 rfc822_write_address (buf, sizeof (buf), adr, display);
1387 len = m_strlen(buf);
1388 if (count && linelen + len > 74) {
1390 linelen = len + 8; /* tab is usually about 8 spaces... */
1393 if (count && adr->mailbox) {
1401 if (!adr->group && adr->next && adr->next->mailbox) {
1411 /* arbitrary number of elements to grow the array by */
1416 /* need to write the list in reverse because they are stored in reverse order
1417 * when parsed to speed up threading
1419 void mutt_write_references (LIST * r, FILE * f)
1422 int refcnt = 0, refmax = 0;
1424 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1425 if (refcnt == refmax)
1426 p_realloc(&ref, refmax += REF_INC);
1430 while (refcnt-- > 0) {
1432 fputs (ref[refcnt]->data, f);
1438 /* Note: all RFC2047 encoding should be done outside of this routine, except
1439 * for the "real name." This will allow this routine to be used more than
1440 * once, if necessary.
1442 * Likewise, all IDN processing should happen outside of this routine.
1444 * mode == 1 => "lite" mode (used for edit_hdrs)
1445 * mode == 0 => normal mode. write full header + MIME headers
1446 * mode == -1 => write just the envelope info (used for postponing messages)
1448 * privacy != 0 => will omit any headers which may identify the user.
1449 * Output generated is suitable for being sent through
1450 * anonymous remailer chains.
1454 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1455 int mode, int privacy)
1457 char buffer[LONG_STRING];
1459 LIST *tmp = env->userhdrs;
1460 int has_agent = 0; /* user defined user-agent header field exists */
1461 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1464 if (!option (OPTNEWSSEND))
1466 if (mode == 0 && !privacy)
1467 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1469 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1471 /* OPTUSEFROM is not consulted here so that we can still write a From:
1472 * field if the user sets it with the `my_hdr' command
1474 if (env->from && !privacy) {
1476 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1477 fprintf (fp, "From: %s\n", buffer);
1482 mutt_write_address_list (env->to, fp, 4, 0);
1486 if (!option (OPTNEWSSEND))
1488 if (EDIT_HEADER("To:"))
1489 fputs ("To:\n", fp);
1493 mutt_write_address_list (env->cc, fp, 4, 0);
1497 if (!option (OPTNEWSSEND))
1499 if (EDIT_HEADER("Cc:"))
1500 fputs ("Cc:\n", fp);
1503 if (mode != 0 || option (OPTWRITEBCC)) {
1504 fputs ("Bcc: ", fp);
1505 mutt_write_address_list (env->bcc, fp, 5, 0);
1510 if (!option (OPTNEWSSEND))
1512 if (EDIT_HEADER("Bcc:"))
1513 fputs ("Bcc:\n", fp);
1516 if (env->newsgroups)
1517 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1518 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1519 fputs ("Newsgroups:\n", fp);
1521 if (env->followup_to)
1522 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1523 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1524 fputs ("Followup-To:\n", fp);
1526 if (env->x_comment_to)
1527 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1528 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1529 EDIT_HEADER("X-Comment-To:"))
1530 fputs ("X-Comment-To:\n", fp);
1534 fprintf (fp, "Subject: %s\n", env->subject);
1535 else if (mode == 1 && EDIT_HEADER("Subject:"))
1536 fputs ("Subject:\n", fp);
1538 /* save message id if the user has set it */
1539 if (env->message_id && !privacy)
1540 fprintf (fp, "Message-ID: %s\n", env->message_id);
1542 if (env->reply_to) {
1543 fputs ("Reply-To: ", fp);
1544 mutt_write_address_list (env->reply_to, fp, 10, 0);
1546 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1547 fputs ("Reply-To:\n", fp);
1549 if (env->mail_followup_to)
1551 if (!option (OPTNEWSSEND))
1554 fputs ("Mail-Followup-To: ", fp);
1555 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1559 if (env->references) {
1560 fputs ("References:", fp);
1561 mutt_write_references (env->references, fp);
1565 /* Add the MIME headers */
1566 fputs ("MIME-Version: 1.0\n", fp);
1567 mutt_write_mime_header (attach, fp);
1570 if (env->in_reply_to) {
1571 fputs ("In-Reply-To:", fp);
1572 mutt_write_references (env->in_reply_to, fp);
1578 /* Add any user defined headers */
1579 for (; tmp; tmp = tmp->next) {
1580 if ((p = strchr (tmp->data, ':'))) {
1581 p = vskipspaces(p + 1);
1583 continue; /* don't emit empty fields. */
1585 /* check to see if the user has overridden the user-agent field */
1586 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1592 fputs (tmp->data, fp);
1597 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1600 if (OperatingSystem != NULL) {
1601 os = OperatingSystem;
1604 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1606 /* Add a vanity header */
1607 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1610 list_del (&hdrs, (list_del_t*)xmemfree);
1612 return (ferror (fp) == 0 ? 0 : -1);
1615 static void encode_headers (LIST * h)
1621 for (; h; h = h->next) {
1622 if (!(p = strchr (h->data, ':')))
1626 p = vskipspaces(p + 1);
1632 rfc2047_encode_string (&tmp);
1633 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1635 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1641 const char *mutt_fqdn (short may_hide_host)
1645 if (Fqdn && Fqdn[0] != '@') {
1648 if (may_hide_host && option (OPTHIDDENHOST)) {
1649 if ((p = strchr (Fqdn, '.')))
1652 /* sanity check: don't hide the host if
1653 * the fqdn is something like detebe.org.
1656 if (!p || !(q = strchr (p, '.')))
1664 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1665 static char mutt_normalized_char(char c)
1667 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1670 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1672 #define APPEND_FMT(fmt, arg) \
1674 int snlen = snprintf(buf, len, fmt, arg); \
1679 #define APPEND_BYTE(c) \
1696 APPEND_BYTE(mutt_normalized_char(c));
1704 APPEND_FMT("%02d", tm->tm_mday);
1707 APPEND_FMT("%02d", tm->tm_hour);
1710 APPEND_FMT("%02d", tm->tm_mon + 1);
1713 APPEND_FMT("%02d", tm->tm_min);
1716 APPEND_FMT("%lo", (unsigned long)now);
1719 APPEND_FMT("%u", (unsigned int)getpid());
1722 APPEND_FMT("%c", MsgIdPfx);
1723 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1726 APPEND_FMT("%u", (unsigned int)rand());
1729 APPEND_FMT("%x", (unsigned int)rand());
1732 APPEND_FMT("%02d", tm->tm_sec);
1735 APPEND_FMT("%u", (unsigned int) now);
1738 APPEND_FMT("%x", (unsigned int) now);
1740 case 'Y': /* this will break in the year 10000 ;-) */
1741 APPEND_FMT("%04d", tm->tm_year + 1900);
1746 default: /* invalid formats are replaced by '.' */
1748 m_strncat(buf, len, ".", 1);
1758 char *mutt_gen_msgid (void)
1760 char buf[SHORT_STRING];
1761 char localpart[SHORT_STRING];
1762 unsigned int localpart_length;
1765 if (!(fqdn = mutt_fqdn (0)))
1766 fqdn = NONULL (Hostname);
1768 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1770 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1772 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1773 return (m_strdup(buf));
1776 static RETSIGTYPE alarm_handler (int sig)
1781 /* invoke sendmail in a subshell
1782 path (in) path to program to execute
1783 args (in) arguments to pass to program
1784 msg (in) temp file containing message to send
1785 tempfile (out) if sendmail is put in the background, this points
1786 to the temporary file containing the stdout of the
1789 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1795 mutt_block_signals_system ();
1798 /* we also don't want to be stopped right now */
1799 sigaddset (&set, SIGTSTP);
1800 sigprocmask (SIG_BLOCK, &set, NULL);
1802 if (SendmailWait >= 0) {
1803 char tmp[_POSIX_PATH_MAX];
1806 *tempfile = m_strdup(tmp);
1809 if ((pid = fork ()) == 0) {
1810 struct sigaction act, oldalrm;
1812 /* save parent's ID before setsid() */
1815 /* we want the delivery to continue even after the main process dies,
1816 * so we put ourselves into another session right away
1820 /* next we close all open files */
1821 #if defined(OPEN_MAX)
1822 for (fd = 0; fd < OPEN_MAX; fd++)
1824 #elif defined(_POSIX_OPEN_MAX)
1825 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1833 /* now the second fork() */
1834 if ((pid = fork ()) == 0) {
1835 /* "msg" will be opened as stdin */
1836 if (open (msg, O_RDONLY, 0) < 0) {
1842 if (SendmailWait >= 0) {
1843 /* *tempfile will be opened as stdout */
1844 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1847 /* redirect stderr to *tempfile too */
1852 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1854 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1861 else if (pid == -1) {
1867 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1868 * SendmailWait = 0: wait forever
1869 * SendmailWait < 0: don't wait
1871 if (SendmailWait > 0) {
1873 act.sa_handler = alarm_handler;
1875 /* need to make sure waitpid() is interrupted on SIGALRM */
1876 act.sa_flags = SA_INTERRUPT;
1880 sigemptyset (&act.sa_mask);
1881 sigaction (SIGALRM, &act, &oldalrm);
1882 alarm (SendmailWait);
1884 else if (SendmailWait < 0)
1885 _exit (0xff & EX_OK);
1887 if (waitpid (pid, &st, 0) > 0) {
1888 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1889 if (SendmailWait && st == (0xff & EX_OK)) {
1890 unlink (*tempfile); /* no longer needed */
1895 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1896 if (SendmailWait > 0) {
1902 /* reset alarm; not really needed, but... */
1904 sigaction (SIGALRM, &oldalrm, NULL);
1906 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1907 /* the parent is already dead */
1915 sigprocmask (SIG_UNBLOCK, &set, NULL);
1917 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1918 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1920 st = S_ERR; /* error */
1922 mutt_unblock_signals_system (1);
1927 static const char **
1928 add_args(const char **args, size_t *argslen, size_t *argsmax, address_t * addr)
1930 for (; addr; addr = addr->next) {
1931 /* weed out group mailboxes, since those are for display only */
1932 if (addr->mailbox && !addr->group) {
1933 if (*argslen == *argsmax)
1934 p_realloc(&args, *argsmax += 5);
1935 args[(*argslen)++] = addr->mailbox;
1941 static const char **
1942 add_option(const char **args, size_t *argslen, size_t *argsmax, const char *s)
1944 if (*argslen == *argsmax) {
1945 p_realloc(&args, *argsmax += 5);
1947 args[(*argslen)++] = s;
1951 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1952 address_t * to, address_t * cc, address_t * bcc, /* recips */
1953 const char *msg, /* file containing message */
1955 { /* message contains 8bit chars */
1956 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1957 const char **args = NULL;
1958 size_t argslen = 0, argsmax = 0;
1962 if (option (OPTNEWSSEND)) {
1963 char cmd[LONG_STRING];
1965 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1968 i = nntp_post (msg);
1977 s = m_strdup(Sendmail);
1981 while ((ps = strtok (ps, " "))) {
1982 if (argslen == argsmax)
1983 p_realloc(&args, argsmax += 5);
1986 args[argslen++] = ps;
1988 path = m_strdup(ps);
1989 ps = strrchr (ps, '/');
1994 args[argslen++] = ps;
2001 if (!option (OPTNEWSSEND)) {
2003 if (eightbit && option (OPTUSE8BITMIME))
2004 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
2006 if (option (OPTENVFROM)) {
2007 address_t *f = NULL;
2010 else if (from && !from->next)
2013 args = add_option (args, &argslen, &argsmax, "-f");
2014 args = add_args (args, &argslen, &argsmax, f);
2018 args = add_option (args, &argslen, &argsmax, "-N");
2019 args = add_option (args, &argslen, &argsmax, DsnNotify);
2022 args = add_option (args, &argslen, &argsmax, "-R");
2023 args = add_option (args, &argslen, &argsmax, DsnReturn);
2025 args = add_option (args, &argslen, &argsmax, "--");
2026 args = add_args (args, &argslen, &argsmax, to);
2027 args = add_args (args, &argslen, &argsmax, cc);
2028 args = add_args (args, &argslen, &argsmax, bcc);
2033 if (argslen == argsmax)
2034 p_realloc(&args, ++argsmax);
2036 args[argslen++] = NULL;
2038 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2040 const char *e = mutt_strsysexit (i);
2042 e = mutt_strsysexit (i);
2043 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2048 if (stat (childout, &st) == 0 && st.st_size > 0)
2049 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2057 p_delete(&childout);
2062 if (i == (EX_OK & 0xff))
2064 else if (i == S_BKG)
2071 int mutt_invoke_mta (address_t * from, /* the sender */
2072 address_t * to, address_t * cc, address_t * bcc, /* recips */
2073 const char *msg, /* file containing message */
2075 { /* message contains 8bit chars */
2078 if (!option (OPTNEWSSEND))
2081 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2084 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2087 /* appends string 'b' to string 'a', and returns the pointer to the new
2089 char *mutt_append_string (char *a, const char *b)
2091 size_t la = m_strlen(a);
2093 p_realloc(&a, la + m_strlen(b) + 1);
2094 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2098 /* returns 1 if char `c' needs to be quoted to protect from shell
2099 interpretation when executing commands in a subshell */
2100 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2102 /* returns 1 if string `s' contains characters which could cause problems
2103 when used on a command line to execute a command */
2104 int mutt_needs_quote (const char *s)
2107 if (INVALID_CHAR (*s))
2114 /* Quote a string to prevent shell escapes when this string is used on the
2115 command line to send mail. */
2116 char *mutt_quote_string (const char *s)
2121 rlen = m_strlen(s) + 3;
2122 pr = r = p_new(char, rlen);
2125 if (INVALID_CHAR (*s)) {
2128 p_realloc(&r, ++rlen);
2139 /* For postponing (!final) do the necessary encodings only */
2140 void mutt_prepare_envelope (ENVELOPE * env, int final)
2142 char buffer[LONG_STRING];
2145 if (env->bcc && !(env->to || env->cc)) {
2146 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2147 * recipients if there is no To: or Cc: field, so attempt to suppress
2148 * it by using an empty To: field.
2150 env->to = address_new ();
2152 env->to->next = address_new ();
2155 rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
2158 env->to->mailbox = m_strdup(buffer);
2161 mutt_set_followup_to (env);
2163 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2164 env->message_id = mutt_gen_msgid ();
2167 /* Take care of 8-bit => 7-bit conversion. */
2168 rfc2047_encode_adrlist (env->to, "To");
2169 rfc2047_encode_adrlist (env->cc, "Cc");
2170 rfc2047_encode_adrlist (env->bcc, "Bcc");
2171 rfc2047_encode_adrlist (env->from, "From");
2172 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2173 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2177 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2180 rfc2047_encode_string (&env->subject);
2182 encode_headers (env->userhdrs);
2185 void mutt_unprepare_envelope (ENVELOPE * env)
2189 for (item = env->userhdrs; item; item = item->next)
2190 rfc2047_decode (&item->data);
2192 address_delete (&env->mail_followup_to);
2194 /* back conversions */
2195 rfc2047_decode_adrlist (env->to);
2196 rfc2047_decode_adrlist (env->cc);
2197 rfc2047_decode_adrlist (env->bcc);
2198 rfc2047_decode_adrlist (env->from);
2199 rfc2047_decode_adrlist (env->reply_to);
2200 rfc2047_decode (&env->subject);
2203 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2204 const char *resent_from, address_t * env_from)
2208 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2209 MESSAGE *msg = NULL;
2212 /* Try to bounce each message out, aborting if we get any failures. */
2213 for (i = 0; i < Context->msgcount; i++)
2214 if (Context->hdrs[i]->tagged)
2216 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2221 /* If we failed to open a message, return with error */
2222 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2228 mutt_mktemp (tempfile);
2229 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2230 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2232 if (!option (OPTBOUNCEDELIVERED))
2233 ch_flags |= CH_WEED_DELIVERED;
2235 fseeko (fp, h->offset, 0);
2236 fprintf (f, "Resent-From: %s", resent_from);
2237 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2238 if (MsgIdFormat && *MsgIdFormat)
2239 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2240 fputs ("Resent-To: ", f);
2241 mutt_write_address_list (to, f, 11, 0);
2242 mutt_copy_header (fp, h, f, ch_flags, NULL);
2244 mutt_copy_bytes (fp, f, h->content->length);
2247 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2248 h->content->encoding == ENC8BIT);
2252 mx_close_message (&msg);
2257 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2260 const char *fqdn = mutt_fqdn (1);
2261 char resent_from[STRING];
2265 resent_from[0] = '\0';
2266 from = mutt_default_from ();
2269 rfc822_qualify (from, fqdn);
2271 rfc2047_encode_adrlist (from, "Resent-From");
2272 if (mutt_addrlist_to_idna (from, &err)) {
2273 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2276 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2279 unset_option (OPTNEWSSEND);
2282 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2284 address_delete (&from);
2290 /* given a list of addresses, return a list of unique addresses */
2291 address_t *mutt_remove_duplicates (address_t * addr)
2293 address_t *top = addr;
2294 address_t **last = ⊤
2299 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2300 if (tmp->mailbox && addr->mailbox &&
2301 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2308 debug_print (2, ("Removing %s\n", addr->mailbox));
2313 address_delete (&addr);
2326 static void set_noconv_flags (BODY * b, short flag)
2328 for (; b; b = b->next) {
2329 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2330 set_noconv_flags (b->parts, flag);
2331 else if (b->type == TYPETEXT && b->noconv) {
2333 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2335 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2340 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2341 int post, char *fcc)
2345 char tempfile[_POSIX_PATH_MAX];
2346 FILE *tempfp = NULL;
2350 set_noconv_flags (hdr->content, 1);
2352 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2353 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2357 /* We need to add a Content-Length field to avoid problems where a line in
2358 * the message body begins with "From "
2360 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2361 mutt_mktemp (tempfile);
2362 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2363 mutt_perror (tempfile);
2364 mx_close_mailbox (&f, NULL);
2369 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2370 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2371 mx_close_mailbox (&f, NULL);
2375 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2376 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2378 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2381 /* (postponment) if this was a reply of some sort, <msgid> contians the
2382 * Message-ID: of message replied to. Save it using a special X-Mutt-
2383 * header so it can be picked up if the message is recalled at a later
2384 * point in time. This will allow the message to be marked as replied if
2385 * the same mailbox is still open.
2388 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2390 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2391 * it can be picked up when the message is recalled
2394 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2395 fprintf (msg->fp, "Status: RO\n");
2399 /* (postponment) if the mail is to be signed or encrypted, save this info */
2400 if ((WithCrypto & APPLICATION_PGP)
2401 && post && (hdr->security & APPLICATION_PGP)) {
2402 fputs ("X-Mutt-PGP: ", msg->fp);
2403 if (hdr->security & ENCRYPT)
2404 fputc ('E', msg->fp);
2405 if (hdr->security & SIGN) {
2406 fputc ('S', msg->fp);
2407 if (PgpSignAs && *PgpSignAs)
2408 fprintf (msg->fp, "<%s>", PgpSignAs);
2410 if (hdr->security & INLINE)
2411 fputc ('I', msg->fp);
2412 fputc ('\n', msg->fp);
2415 /* (postponment) if the mail is to be signed or encrypted, save this info */
2416 if ((WithCrypto & APPLICATION_SMIME)
2417 && post && (hdr->security & APPLICATION_SMIME)) {
2418 fputs ("X-Mutt-SMIME: ", msg->fp);
2419 if (hdr->security & ENCRYPT) {
2420 fputc ('E', msg->fp);
2421 if (SmimeCryptAlg && *SmimeCryptAlg)
2422 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2424 if (hdr->security & SIGN) {
2425 fputc ('S', msg->fp);
2426 if (SmimeDefaultKey && *SmimeDefaultKey)
2427 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2429 if (hdr->security & INLINE)
2430 fputc ('I', msg->fp);
2431 fputc ('\n', msg->fp);
2435 /* (postponement) if the mail is to be sent through a mixmaster
2436 * chain, save that information
2439 if (post && hdr->chain && hdr->chain) {
2442 fputs ("X-Mutt-Mix:", msg->fp);
2443 for (p = hdr->chain; p; p = p->next)
2444 fprintf (msg->fp, " %s", (char *) p->data);
2446 fputc ('\n', msg->fp);
2451 char sasha[LONG_STRING];
2454 mutt_write_mime_body (hdr->content, tempfp);
2456 /* make sure the last line ends with a newline. Emacs doesn't ensure
2457 * this will happen, and it can cause problems parsing the mailbox
2460 fseeko (tempfp, -1, 2);
2461 if (fgetc (tempfp) != '\n') {
2462 fseeko (tempfp, 0, 2);
2463 fputc ('\n', tempfp);
2467 if (ferror (tempfp)) {
2468 debug_print (1, ("%s: write failed.\n", tempfile));
2471 mx_commit_message (msg, &f); /* XXX - really? */
2472 mx_close_message (&msg);
2473 mx_close_mailbox (&f, NULL);
2477 /* count the number of lines */
2479 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2481 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2482 fprintf (msg->fp, "Lines: %d\n\n", lines);
2484 /* copy the body and clean up */
2486 r = mutt_copy_stream (tempfp, msg->fp);
2487 if (fclose (tempfp) != 0)
2489 /* if there was an error, leave the temp version */
2494 fputc ('\n', msg->fp); /* finish off the header */
2495 r = mutt_write_mime_body (hdr->content, msg->fp);
2498 if (mx_commit_message (msg, &f) != 0)
2500 mx_close_message (&msg);
2501 mx_close_mailbox (&f, NULL);
2504 set_noconv_flags (hdr->content, 0);