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"
33 #include "mutt_crypt.h"
34 #include "mutt_idna.h"
36 #include "lib/debug.h"
47 #include <sys/utsname.h>
50 # include "mutt_libesmtp.h"
51 #endif /* USE_LIBESMTP */
57 #ifdef HAVE_SYSEXITS_H
59 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
63 /* If you are debugging this file, comment out the following line. */
72 extern char RFC822Specials[];
74 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
76 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
78 static char MsgIdPfx = 'A';
80 static void transform_to_7bit (BODY * a, FILE * fpin);
82 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
85 char line[77], savechar;
87 while ((c = fgetconv (fc)) != EOF) {
88 /* Wrap the line if needed. */
89 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
90 /* If the last character is "quoted", then be sure to move all three
91 * characters to the next line. Otherwise, just move the last
94 if (line[linelen - 3] == '=') {
95 line[linelen - 3] = 0;
100 line[1] = line[linelen - 2];
101 line[2] = line[linelen - 1];
105 savechar = line[linelen - 1];
106 line[linelen - 1] = '=';
115 /* Escape lines that begin with/only contain "the message separator". */
116 if (linelen == 4 && !m_strncmp("From", line, 4)) {
117 m_strcpy(line, sizeof(line), "=46rom");
120 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
121 m_strcpy(line, sizeof(line), "=66rom");
124 else if (linelen == 1 && line[0] == '.') {
125 m_strcpy(line, sizeof(line), "=2E");
130 if (c == '\n' && istext) {
131 /* Check to make sure there is no trailing space on this line. */
133 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
135 sprintf (line + linelen - 1, "=%2.2X",
136 (unsigned char) line[linelen - 1]);
140 int savechar = line[linelen - 1];
142 line[linelen - 1] = '=';
145 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
155 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
156 /* Check to make sure there is enough room for the quoted character.
157 * If not, wrap to the next line.
160 line[linelen++] = '=';
166 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
170 /* Don't worry about wrapping the line here. That will happen during
171 * the next iteration when I'll also know what the next character is.
177 /* Take care of anything left in the buffer */
179 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
180 /* take care of trailing whitespace */
182 sprintf (line + linelen - 1, "=%2.2X",
183 (unsigned char) line[linelen - 1]);
185 savechar = line[linelen - 1];
186 line[linelen - 1] = '=';
190 sprintf (line, "=%2.2X", (unsigned char) savechar);
199 static char b64_buffer[3];
200 static short b64_num;
201 static short b64_linelen;
203 static void b64_flush (FILE * fout)
210 if (b64_linelen >= 72) {
215 for (i = b64_num; i < 3; i++)
216 b64_buffer[i] = '\0';
218 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
221 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
226 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
230 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
235 while (b64_linelen % 4) {
244 static void b64_putc (char c, FILE * fout)
249 b64_buffer[b64_num++] = c;
253 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
257 b64_num = b64_linelen = 0;
259 while ((ch = fgetconv (fc)) != EOF) {
260 if (istext && ch == '\n' && ch1 != '\r')
261 b64_putc ('\r', fout);
269 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
273 while ((ch = fgetconv (fc)) != EOF)
278 int mutt_write_mime_header (BODY * a, FILE * f)
288 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
291 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
293 for (p = a->parameter; p; p = p->next) {
302 tmp = m_strdup(p->value);
303 encode = rfc2231_encode_string (&tmp);
304 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
306 /* Dirty hack to make messages readable by Outlook Express
307 * for the Mac: force quotes around the boundary parameter
308 * even when they aren't needed.
311 if (!ascii_strcasecmp (p->attribute, "boundary")
312 && !strcmp (buffer, tmp))
313 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
317 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
319 if (len + tmplen + 2 > 76) {
328 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
336 fprintf (f, "Content-Description: %s\n", a->description);
338 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
341 if (!(fn = a->d_filename))
347 /* Strip off the leading path... */
348 if ((t = strrchr (fn, '/')))
355 encode = rfc2231_encode_string (&tmp);
356 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
358 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
364 if (a->encoding != ENC7BIT)
365 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
367 /* Do NOT add the terminator here!!! */
368 return (ferror (f) ? -1 : 0);
371 # define write_as_text_part(a) (mutt_is_text_part(a) \
372 || ((WithCrypto & APPLICATION_PGP)\
373 && mutt_is_application_pgp(a)))
375 int mutt_write_mime_body (BODY * a, FILE * f)
377 char *p, boundary[SHORT_STRING];
378 char send_charset[SHORT_STRING];
383 if (a->type == TYPEMULTIPART) {
384 /* First, find the boundary to use */
385 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
386 debug_print (1, ("no boundary parameter found!\n"));
387 mutt_error _("No boundary parameter found! [report this error]");
391 m_strcpy(boundary, sizeof(boundary), p);
393 for (t = a->parts; t; t = t->next) {
394 fprintf (f, "\n--%s\n", boundary);
395 if (mutt_write_mime_header (t, f) == -1)
398 if (mutt_write_mime_body (t, f) == -1)
401 fprintf (f, "\n--%s--\n", boundary);
402 return (ferror (f) ? -1 : 0);
405 /* This is pretty gross, but it's the best solution for now... */
406 if ((WithCrypto & APPLICATION_PGP)
407 && a->type == TYPEAPPLICATION
408 && m_strcmp(a->subtype, "pgp-encrypted") == 0) {
409 fputs ("Version: 1\n", f);
413 if ((fpin = fopen (a->filename, "r")) == NULL) {
414 debug_print (1, ("%s no longer exists!\n", a->filename));
415 mutt_error (_("%s no longer exists!"), a->filename);
419 if (a->type == TYPETEXT && (!a->noconv))
420 fc = fgetconv_open (fpin, a->file_charset,
421 mutt_get_body_charset (send_charset,
422 sizeof (send_charset), a), 0);
424 fc = fgetconv_open (fpin, 0, 0, 0);
426 if (a->encoding == ENCQUOTEDPRINTABLE)
427 encode_quoted (fc, f, write_as_text_part (a));
428 else if (a->encoding == ENCBASE64)
429 encode_base64 (fc, f, write_as_text_part (a));
430 else if (a->type == TYPETEXT && (!a->noconv))
431 encode_8bit (fc, f, write_as_text_part (a));
433 mutt_copy_stream (fpin, f);
435 fgetconv_close (&fc);
438 return (ferror (f) ? -1 : 0);
441 #undef write_as_text_part
443 #define BOUNDARYLEN 16
444 void mutt_generate_boundary (PARAMETER ** parm)
446 char rs[BOUNDARYLEN + 1];
451 for (i = 0; i < BOUNDARYLEN; i++)
452 *p++ = __m_b64chars[LRAND() % sizeof(__m_b64chars)];
455 mutt_set_parameter ("boundary", rs, parm);
467 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
471 int whitespace = s->whitespace;
473 int linelen = s->linelen;
474 int was_cr = s->was_cr;
476 if (!d) { /* This signals EOF */
479 if (linelen > info->linemax)
480 info->linemax = linelen;
485 for (; dlen; d++, dlen--) {
498 if (linelen > info->linemax)
499 info->linemax = linelen;
514 if (linelen > info->linemax)
515 info->linemax = linelen;
520 else if (ch == '\r') {
528 else if (ch == '\t' || ch == '\f') {
532 else if (ch < 32 || ch == 127)
536 if ((ch == 'F') || (ch == 'f'))
546 if (linelen == 2 && ch != 'r')
548 else if (linelen == 3 && ch != 'o')
550 else if (linelen == 4) {
563 if (ch != ' ' && ch != '\t')
568 s->whitespace = whitespace;
570 s->linelen = linelen;
575 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
576 #define BUGGY_ICONV 1
579 * Find the best charset conversion of the file from fromcode into one
580 * of the tocodes. If successful, set *tocode and CONTENT *info and
581 * return the number of characters converted inexactly. If no
582 * conversion was possible, return -1.
584 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
585 * which would otherwise prevent us from knowing the number of inexact
586 * conversions. Where the candidate target charset is UTF-8 we avoid
587 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
588 * fails with some libraries.
590 * We assume that the output from iconv is never more than 4 times as
591 * long as the input for any pair of charsets we might be interested
594 static size_t convert_file_to (FILE * file, const char *fromcode,
595 int ncodes, const char **tocodes,
596 int *tocode, CONTENT * info)
600 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
603 size_t ibl, obl, ubl, ubl1, n, ret;
606 CONTENT_STATE *states;
609 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
610 if (cd1 == (iconv_t) (-1))
613 cd = p_new(iconv_t, ncodes);
614 score = p_new(size_t, ncodes);
615 states = p_new(CONTENT_STATE, ncodes);
616 infos = p_new(CONTENT, ncodes);
618 for (i = 0; i < ncodes; i++)
619 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
620 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
622 /* Special case for conversion to UTF-8 */
623 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
629 /* Try to fill input buffer */
630 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
633 /* Convert to UTF-8 */
635 ob = bufu, obl = sizeof (bufu);
636 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
637 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
638 if (n == (size_t) (-1) &&
639 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
640 assert (errno == EILSEQ ||
641 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
647 /* Convert from UTF-8 */
648 for (i = 0; i < ncodes; i++)
649 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
650 ub = bufu, ubl = ubl1;
651 ob = bufo, obl = sizeof (bufo);
652 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
653 if (n == (size_t) (-1)) {
654 assert (errno == E2BIG ||
655 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
656 score[i] = (size_t) (-1);
660 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
663 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
664 /* Special case for conversion to UTF-8 */
665 update_content_info (&infos[i], &states[i], bufu, ubl1);
668 /* Save unused input */
669 memmove (bufi, ib, ibl);
670 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
677 /* Find best score */
679 for (i = 0; i < ncodes; i++) {
680 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
681 /* Special case for conversion to UTF-8 */
686 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
688 else if (ret == (size_t) (-1) || score[i] < ret) {
695 if (ret != (size_t) (-1)) {
696 memcpy (info, &infos[*tocode], sizeof (CONTENT));
697 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
701 for (i = 0; i < ncodes; i++)
702 if (cd[i] != (iconv_t) (-1))
714 #endif /* !HAVE_ICONV */
718 * Find the first of the fromcodes that gives a valid conversion and
719 * the best charset conversion of the file into one of the tocodes. If
720 * successful, set *fromcode and *tocode to dynamically allocated
721 * strings, set CONTENT *info, and return the number of characters
722 * converted inexactly. If no conversion was possible, return -1.
724 * Both fromcodes and tocodes may be colon-separated lists of charsets.
725 * However, if fromcode is zero then fromcodes is assumed to be the
726 * name of a single charset even if it contains a colon.
728 static size_t convert_file_from_to (FILE * file,
729 const char *fromcodes,
730 const char *tocodes, char **fromcode,
731 char **tocode, CONTENT * info)
739 /* Count the tocodes */
741 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
742 if ((c1 = strchr (c, ':')) == c)
748 tcode = p_new(char *, ncodes);
749 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
750 if ((c1 = strchr (c, ':')) == c)
752 tcode[i] = str_substrdup (c, c1);
757 /* Try each fromcode in turn */
758 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
759 if ((c1 = strchr (c, ':')) == c)
761 fcode = str_substrdup (c, c1);
763 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
765 if (ret != (size_t) (-1)) {
775 /* There is only one fromcode */
776 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
778 if (ret != (size_t) (-1)) {
785 for (i = 0; i < ncodes; i++)
794 * Analyze the contents of a file to determine which MIME encoding to use.
795 * Also set the body charset, sometimes, or not.
797 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
802 char *fromcode = NULL;
813 if (stat (fname, &sb) == -1) {
814 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
818 if (!S_ISREG (sb.st_mode)) {
819 mutt_error (_("%s isn't a regular file."), fname);
823 if ((fp = fopen (fname, "r")) == NULL) {
824 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
828 info = p_new(CONTENT, 1);
831 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
832 char *chs = mutt_get_parameter ("charset", b->parameter);
833 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
834 FileCharset : Charset) : Charset;
835 if (Charset && (chs || SendCharset) &&
836 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
837 &fromcode, &tocode, info) != (size_t) (-1)) {
839 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
840 mutt_set_parameter ("charset", chsbuf, &b->parameter);
842 b->file_charset = fromcode;
850 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
851 update_content_info (info, &state, buffer, r);
852 update_content_info (info, &state, 0, 0);
856 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
857 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
859 && !mutt_is_us_ascii (Charset) ? Charset :
860 "unknown-8bit"), &b->parameter);
865 /* Given a file with path ``s'', see if there is a registered MIME type.
866 * returns the major MIME type, and copies the subtype to ``d''. First look
867 * for ~/.mime.types, then look in a system mime.types if we can find one.
868 * The longest match is used so that we can match `ps.gz' when `gz' also
872 int mutt_lookup_mime_type (BODY * att, const char *path)
876 char buf[LONG_STRING];
877 char subtype[STRING], xtype[STRING];
879 int szf, sze, cur_sze;
887 szf = m_strlen(path);
889 for (count = 0; count < 4; count++) {
891 * can't use strtok() because we use it in an inner loop below, so use
892 * a switch statement here instead.
896 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
899 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/muttng-mime.types");
902 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
905 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
908 debug_print (1, ("Internal error, count = %d.\n", count));
909 goto bye; /* shouldn't happen */
912 if ((f = fopen (buf, "r")) != NULL) {
913 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
914 /* weed out any comments */
915 if ((p = strchr (buf, '#')))
918 /* remove any leading space. */
919 ct = vskipspaces(buf);
921 /* position on the next field in this line */
922 if ((p = strpbrk (ct, " \t")) == NULL)
927 /* cycle through the file extensions */
928 while ((p = strtok (p, " \t\n"))) {
930 if ((sze > cur_sze) && (szf >= sze) &&
931 (m_strcasecmp(path + szf - sze, p) == 0
932 || ascii_strcasecmp (path + szf - sze, p) == 0)
933 && (szf == sze || path[szf - sze - 1] == '.'))
935 /* get the content-type */
937 if ((p = strchr (ct, '/')) == NULL) {
938 /* malformed line, just skip it. */
943 for (q = p; *q && !ISSPACE (*q); q++);
945 str_substrcpy (subtype, p, q, sizeof (subtype));
947 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
948 m_strcpy(xtype, sizeof(xtype), ct);
961 if (type != TYPEOTHER || *xtype != '\0') {
963 str_replace (&att->subtype, subtype);
964 str_replace (&att->xtype, xtype);
970 void mutt_message_to_7bit (BODY * a, FILE * fp)
972 char temp[_POSIX_PATH_MAX];
978 if (!a->filename && fp)
980 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
981 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
986 if (stat (a->filename, &sb) == -1) {
987 mutt_perror ("stat");
990 a->length = sb.st_size;
994 if (!(fpout = safe_fopen (temp, "w+"))) {
995 mutt_perror ("fopen");
999 fseeko (fpin, a->offset, 0);
1000 a->parts = mutt_parse_messageRFC822 (fpin, a);
1002 transform_to_7bit (a->parts, fpin);
1004 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1005 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1007 fputs ("MIME-Version: 1.0\n", fpout);
1008 mutt_write_mime_header (a->parts, fpout);
1009 fputc ('\n', fpout);
1010 mutt_write_mime_body (a->parts, fpout);
1022 a->encoding = ENC7BIT;
1023 a->d_filename = a->filename;
1024 if (a->filename && a->unlink)
1025 unlink (a->filename);
1026 a->filename = m_strdup(temp);
1028 if (stat (a->filename, &sb) == -1) {
1029 mutt_perror ("stat");
1032 a->length = sb.st_size;
1033 mutt_free_body (&a->parts);
1034 a->hdr->content = NULL;
1037 static void transform_to_7bit (BODY * a, FILE * fpin)
1039 char buff[_POSIX_PATH_MAX];
1044 for (; a; a = a->next) {
1045 if (a->type == TYPEMULTIPART) {
1046 if (a->encoding != ENC7BIT)
1047 a->encoding = ENC7BIT;
1049 transform_to_7bit (a->parts, fpin);
1051 else if (mutt_is_message_type (a->type, a->subtype)) {
1052 mutt_message_to_7bit (a, fpin);
1056 a->force_charset = 1;
1059 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1060 mutt_perror ("fopen");
1064 mutt_decode_attachment (a, &s);
1066 a->d_filename = a->filename;
1067 a->filename = m_strdup(buff);
1069 if (stat (a->filename, &sb) == -1) {
1070 mutt_perror ("stat");
1073 a->length = sb.st_size;
1075 mutt_update_encoding (a);
1076 if (a->encoding == ENC8BIT)
1077 a->encoding = ENCQUOTEDPRINTABLE;
1078 else if (a->encoding == ENCBINARY)
1079 a->encoding = ENCBASE64;
1084 /* determine which Content-Transfer-Encoding to use */
1085 static void mutt_set_encoding (BODY * b, CONTENT * info)
1087 char send_charset[SHORT_STRING];
1089 if (b->type == TYPETEXT) {
1091 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1092 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1093 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1094 b->encoding = ENCQUOTEDPRINTABLE;
1095 else if (info->hibin)
1096 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1098 b->encoding = ENC7BIT;
1100 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1101 if (info->lobin || info->hibin) {
1102 if (option (OPTALLOW8BIT) && !info->lobin)
1103 b->encoding = ENC8BIT;
1105 mutt_message_to_7bit (b, NULL);
1108 b->encoding = ENC7BIT;
1110 else if (b->type == TYPEAPPLICATION
1111 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1112 b->encoding = ENC7BIT;
1115 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1116 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1119 /* Determine which encoding is smaller */
1120 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1121 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1122 b->encoding = ENCBASE64;
1124 b->encoding = ENCQUOTEDPRINTABLE;
1128 b->encoding = ENC7BIT;
1132 void mutt_stamp_attachment (BODY * a)
1134 a->stamp = time (NULL);
1137 /* Get a body's character set */
1139 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1143 if (b && b->type != TYPETEXT)
1147 p = mutt_get_parameter ("charset", b->parameter);
1150 mutt_canonical_charset (d, dlen, NONULL (p));
1152 m_strcpy(d, dlen, "us-ascii");
1158 /* Assumes called from send mode where BODY->filename points to actual file */
1159 void mutt_update_encoding (BODY * a)
1162 char chsbuff[STRING];
1164 /* override noconv when it's us-ascii */
1165 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1168 if (!a->force_charset && !a->noconv)
1169 mutt_delete_parameter ("charset", &a->parameter);
1171 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1174 mutt_set_encoding (a, info);
1175 mutt_stamp_attachment (a);
1177 p_delete(&a->content);
1182 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1184 char buffer[LONG_STRING];
1187 int cmflags, chflags;
1188 int pgp = WithCrypto ? hdr->security : 0;
1191 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1192 (hdr->security & ENCRYPT)) {
1193 if (!crypt_valid_passphrase (hdr->security))
1198 mutt_mktemp (buffer);
1199 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1202 body = mutt_new_body ();
1203 body->type = TYPEMESSAGE;
1204 body->subtype = m_strdup("rfc822");
1205 body->filename = m_strdup(buffer);
1208 body->disposition = DISPINLINE;
1211 mutt_parse_mime_message (ctx, hdr);
1216 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1217 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1218 chflags |= CH_MIME | CH_TXTPLAIN;
1219 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1220 if ((WithCrypto & APPLICATION_PGP))
1222 if ((WithCrypto & APPLICATION_SMIME))
1223 pgp &= ~SMIMEENCRYPT;
1225 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1226 if ((WithCrypto & APPLICATION_PGP)
1227 && mutt_is_multipart_encrypted (hdr->content)) {
1228 chflags |= CH_MIME | CH_NONEWLINE;
1229 cmflags = M_CM_DECODE_PGP;
1232 else if ((WithCrypto & APPLICATION_PGP)
1233 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1234 chflags |= CH_MIME | CH_TXTPLAIN;
1235 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1238 else if ((WithCrypto & APPLICATION_SMIME)
1239 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1240 chflags |= CH_MIME | CH_TXTPLAIN;
1241 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1242 pgp &= ~SMIMEENCRYPT;
1246 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1251 body->hdr = mutt_new_header ();
1252 body->hdr->offset = 0;
1253 /* we don't need the user headers here */
1254 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1256 body->hdr->security = pgp;
1257 mutt_update_encoding (body);
1258 body->parts = body->hdr->content;
1265 BODY *mutt_make_file_attach (const char *path)
1270 att = mutt_new_body ();
1271 att->filename = m_strdup(path);
1273 /* Attempt to determine the appropriate content-type based on the filename
1280 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1281 path)) != TYPEOTHER || *xbuf != '\0') {
1283 att->subtype = m_strdup(buf);
1284 att->xtype = m_strdup(xbuf);
1289 mutt_lookup_mime_type (att, path);
1293 if ((info = mutt_get_content_info (path, att)) == NULL) {
1294 mutt_free_body (&att);
1298 if (!att->subtype) {
1299 if (info->lobin == 0
1300 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1302 * Statistically speaking, there should be more than 10% "lobin"
1303 * chars if this is really a binary file...
1305 att->type = TYPETEXT;
1306 att->subtype = m_strdup("plain");
1309 att->type = TYPEAPPLICATION;
1310 att->subtype = m_strdup("octet-stream");
1314 mutt_update_encoding (att);
1318 static int get_toplevel_encoding (BODY * a)
1322 for (; a; a = a->next) {
1323 if (a->encoding == ENCBINARY)
1325 else if (a->encoding == ENC8BIT)
1332 BODY *mutt_make_multipart (BODY * b)
1336 new = mutt_new_body ();
1337 new->type = TYPEMULTIPART;
1338 new->subtype = m_strdup("mixed");
1339 new->encoding = get_toplevel_encoding (b);
1340 mutt_generate_boundary (&new->parameter);
1342 new->disposition = DISPINLINE;
1348 /* remove the multipart body if it exists */
1349 BODY *mutt_remove_multipart (BODY * b)
1357 mutt_free_body (&t);
1362 char *mutt_make_date (char *s, size_t len)
1364 time_t t = time (NULL);
1365 struct tm *l = localtime (&t);
1366 time_t tz = mutt_local_tz (t);
1370 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1371 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1372 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1373 (int) tz / 60, (int) abs (tz) % 60);
1377 /* wrapper around mutt_write_address() so we can handle very large
1378 recipient lists without needing a huge temporary buffer in memory */
1379 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1383 char buf[LONG_STRING];
1391 rfc822_write_address (buf, sizeof (buf), adr, display);
1392 len = m_strlen(buf);
1393 if (count && linelen + len > 74) {
1395 linelen = len + 8; /* tab is usually about 8 spaces... */
1398 if (count && adr->mailbox) {
1406 if (!adr->group && adr->next && adr->next->mailbox) {
1416 /* arbitrary number of elements to grow the array by */
1421 /* need to write the list in reverse because they are stored in reverse order
1422 * when parsed to speed up threading
1424 void mutt_write_references (LIST * r, FILE * f)
1427 int refcnt = 0, refmax = 0;
1429 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1430 if (refcnt == refmax)
1431 p_realloc(&ref, refmax += REF_INC);
1435 while (refcnt-- > 0) {
1437 fputs (ref[refcnt]->data, f);
1443 /* Note: all RFC2047 encoding should be done outside of this routine, except
1444 * for the "real name." This will allow this routine to be used more than
1445 * once, if necessary.
1447 * Likewise, all IDN processing should happen outside of this routine.
1449 * mode == 1 => "lite" mode (used for edit_hdrs)
1450 * mode == 0 => normal mode. write full header + MIME headers
1451 * mode == -1 => write just the envelope info (used for postponing messages)
1453 * privacy != 0 => will omit any headers which may identify the user.
1454 * Output generated is suitable for being sent through
1455 * anonymous remailer chains.
1459 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1460 int mode, int privacy)
1462 char buffer[LONG_STRING];
1464 LIST *tmp = env->userhdrs;
1465 int has_agent = 0; /* user defined user-agent header field exists */
1466 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1469 if (!option (OPTNEWSSEND))
1471 if (mode == 0 && !privacy)
1472 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1474 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1476 /* OPTUSEFROM is not consulted here so that we can still write a From:
1477 * field if the user sets it with the `my_hdr' command
1479 if (env->from && !privacy) {
1481 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1482 fprintf (fp, "From: %s\n", buffer);
1487 mutt_write_address_list (env->to, fp, 4, 0);
1491 if (!option (OPTNEWSSEND))
1493 if (EDIT_HEADER("To:"))
1494 fputs ("To:\n", fp);
1498 mutt_write_address_list (env->cc, fp, 4, 0);
1502 if (!option (OPTNEWSSEND))
1504 if (EDIT_HEADER("Cc:"))
1505 fputs ("Cc:\n", fp);
1508 if (mode != 0 || option (OPTWRITEBCC)) {
1509 fputs ("Bcc: ", fp);
1510 mutt_write_address_list (env->bcc, fp, 5, 0);
1515 if (!option (OPTNEWSSEND))
1517 if (EDIT_HEADER("Bcc:"))
1518 fputs ("Bcc:\n", fp);
1521 if (env->newsgroups)
1522 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1523 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1524 fputs ("Newsgroups:\n", fp);
1526 if (env->followup_to)
1527 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1528 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1529 fputs ("Followup-To:\n", fp);
1531 if (env->x_comment_to)
1532 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1533 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1534 EDIT_HEADER("X-Comment-To:"))
1535 fputs ("X-Comment-To:\n", fp);
1539 fprintf (fp, "Subject: %s\n", env->subject);
1540 else if (mode == 1 && EDIT_HEADER("Subject:"))
1541 fputs ("Subject:\n", fp);
1543 /* save message id if the user has set it */
1544 if (env->message_id && !privacy)
1545 fprintf (fp, "Message-ID: %s\n", env->message_id);
1547 if (env->reply_to) {
1548 fputs ("Reply-To: ", fp);
1549 mutt_write_address_list (env->reply_to, fp, 10, 0);
1551 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1552 fputs ("Reply-To:\n", fp);
1554 if (env->mail_followup_to)
1556 if (!option (OPTNEWSSEND))
1559 fputs ("Mail-Followup-To: ", fp);
1560 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1564 if (env->references) {
1565 fputs ("References:", fp);
1566 mutt_write_references (env->references, fp);
1570 /* Add the MIME headers */
1571 fputs ("MIME-Version: 1.0\n", fp);
1572 mutt_write_mime_header (attach, fp);
1575 if (env->in_reply_to) {
1576 fputs ("In-Reply-To:", fp);
1577 mutt_write_references (env->in_reply_to, fp);
1583 /* Add any user defined headers */
1584 for (; tmp; tmp = tmp->next) {
1585 if ((p = strchr (tmp->data, ':'))) {
1586 p = vskipspaces(p + 1);
1588 continue; /* don't emit empty fields. */
1590 /* check to see if the user has overridden the user-agent field */
1591 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1597 fputs (tmp->data, fp);
1602 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1605 if (OperatingSystem != NULL) {
1606 os = OperatingSystem;
1609 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1611 /* Add a vanity header */
1612 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1615 list_del (&hdrs, (list_del_t*)xmemfree);
1617 return (ferror (fp) == 0 ? 0 : -1);
1620 static void encode_headers (LIST * h)
1626 for (; h; h = h->next) {
1627 if (!(p = strchr (h->data, ':')))
1631 p = vskipspaces(p + 1);
1637 rfc2047_encode_string (&tmp);
1638 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1640 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1646 const char *mutt_fqdn (short may_hide_host)
1650 if (Fqdn && Fqdn[0] != '@') {
1653 if (may_hide_host && option (OPTHIDDENHOST)) {
1654 if ((p = strchr (Fqdn, '.')))
1657 /* sanity check: don't hide the host if
1658 * the fqdn is something like detebe.org.
1661 if (!p || !(q = strchr (p, '.')))
1669 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1670 static char mutt_normalized_char(char c)
1672 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1675 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1677 #define APPEND_FMT(fmt, arg) \
1679 int snlen = snprintf(buf, len, fmt, arg); \
1684 #define APPEND_BYTE(c) \
1701 APPEND_BYTE(mutt_normalized_char(c));
1709 APPEND_FMT("%02d", tm->tm_mday);
1712 APPEND_FMT("%02d", tm->tm_hour);
1715 APPEND_FMT("%02d", tm->tm_mon + 1);
1718 APPEND_FMT("%02d", tm->tm_min);
1721 APPEND_FMT("%lo", (unsigned long)now);
1724 APPEND_FMT("%u", (unsigned int)getpid());
1727 APPEND_FMT("%c", MsgIdPfx);
1728 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1731 APPEND_FMT("%u", (unsigned int)rand());
1734 APPEND_FMT("%x", (unsigned int)rand());
1737 APPEND_FMT("%02d", tm->tm_sec);
1740 APPEND_FMT("%u", (unsigned int) now);
1743 APPEND_FMT("%x", (unsigned int) now);
1745 case 'Y': /* this will break in the year 10000 ;-) */
1746 APPEND_FMT("%04d", tm->tm_year + 1900);
1751 default: /* invalid formats are replaced by '.' */
1753 m_strncat(buf, len, ".", 1);
1763 char *mutt_gen_msgid (void)
1765 char buf[SHORT_STRING];
1766 char localpart[SHORT_STRING];
1767 unsigned int localpart_length;
1770 if (!(fqdn = mutt_fqdn (0)))
1771 fqdn = NONULL (Hostname);
1773 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1775 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1777 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1778 return (m_strdup(buf));
1781 static RETSIGTYPE alarm_handler (int sig)
1786 /* invoke sendmail in a subshell
1787 path (in) path to program to execute
1788 args (in) arguments to pass to program
1789 msg (in) temp file containing message to send
1790 tempfile (out) if sendmail is put in the background, this points
1791 to the temporary file containing the stdout of the
1794 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1800 mutt_block_signals_system ();
1803 /* we also don't want to be stopped right now */
1804 sigaddset (&set, SIGTSTP);
1805 sigprocmask (SIG_BLOCK, &set, NULL);
1807 if (SendmailWait >= 0) {
1808 char tmp[_POSIX_PATH_MAX];
1811 *tempfile = m_strdup(tmp);
1814 if ((pid = fork ()) == 0) {
1815 struct sigaction act, oldalrm;
1817 /* save parent's ID before setsid() */
1820 /* we want the delivery to continue even after the main process dies,
1821 * so we put ourselves into another session right away
1825 /* next we close all open files */
1826 #if defined(OPEN_MAX)
1827 for (fd = 0; fd < OPEN_MAX; fd++)
1829 #elif defined(_POSIX_OPEN_MAX)
1830 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1838 /* now the second fork() */
1839 if ((pid = fork ()) == 0) {
1840 /* "msg" will be opened as stdin */
1841 if (open (msg, O_RDONLY, 0) < 0) {
1847 if (SendmailWait >= 0) {
1848 /* *tempfile will be opened as stdout */
1849 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1852 /* redirect stderr to *tempfile too */
1857 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1859 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1866 else if (pid == -1) {
1872 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1873 * SendmailWait = 0: wait forever
1874 * SendmailWait < 0: don't wait
1876 if (SendmailWait > 0) {
1878 act.sa_handler = alarm_handler;
1880 /* need to make sure waitpid() is interrupted on SIGALRM */
1881 act.sa_flags = SA_INTERRUPT;
1885 sigemptyset (&act.sa_mask);
1886 sigaction (SIGALRM, &act, &oldalrm);
1887 alarm (SendmailWait);
1889 else if (SendmailWait < 0)
1890 _exit (0xff & EX_OK);
1892 if (waitpid (pid, &st, 0) > 0) {
1893 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1894 if (SendmailWait && st == (0xff & EX_OK)) {
1895 unlink (*tempfile); /* no longer needed */
1900 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1901 if (SendmailWait > 0) {
1907 /* reset alarm; not really needed, but... */
1909 sigaction (SIGALRM, &oldalrm, NULL);
1911 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1912 /* the parent is already dead */
1920 sigprocmask (SIG_UNBLOCK, &set, NULL);
1922 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1923 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1925 st = S_ERR; /* error */
1927 mutt_unblock_signals_system (1);
1932 static const char **
1933 add_args(const char **args, size_t *argslen, size_t *argsmax, ADDRESS * addr)
1935 for (; addr; addr = addr->next) {
1936 /* weed out group mailboxes, since those are for display only */
1937 if (addr->mailbox && !addr->group) {
1938 if (*argslen == *argsmax)
1939 p_realloc(&args, *argsmax += 5);
1940 args[(*argslen)++] = addr->mailbox;
1946 static const char **
1947 add_option(const char **args, size_t *argslen, size_t *argsmax, const char *s)
1949 if (*argslen == *argsmax) {
1950 p_realloc(&args, *argsmax += 5);
1952 args[(*argslen)++] = s;
1956 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
1957 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
1958 const char *msg, /* file containing message */
1960 { /* message contains 8bit chars */
1961 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1962 const char **args = NULL;
1963 size_t argslen = 0, argsmax = 0;
1967 if (option (OPTNEWSSEND)) {
1968 char cmd[LONG_STRING];
1970 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1973 i = nntp_post (msg);
1982 s = m_strdup(Sendmail);
1986 while ((ps = strtok (ps, " "))) {
1987 if (argslen == argsmax)
1988 p_realloc(&args, argsmax += 5);
1991 args[argslen++] = ps;
1993 path = m_strdup(ps);
1994 ps = strrchr (ps, '/');
1999 args[argslen++] = ps;
2006 if (!option (OPTNEWSSEND)) {
2008 if (eightbit && option (OPTUSE8BITMIME))
2009 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
2011 if (option (OPTENVFROM)) {
2015 else if (from && !from->next)
2018 args = add_option (args, &argslen, &argsmax, "-f");
2019 args = add_args (args, &argslen, &argsmax, f);
2023 args = add_option (args, &argslen, &argsmax, "-N");
2024 args = add_option (args, &argslen, &argsmax, DsnNotify);
2027 args = add_option (args, &argslen, &argsmax, "-R");
2028 args = add_option (args, &argslen, &argsmax, DsnReturn);
2030 args = add_option (args, &argslen, &argsmax, "--");
2031 args = add_args (args, &argslen, &argsmax, to);
2032 args = add_args (args, &argslen, &argsmax, cc);
2033 args = add_args (args, &argslen, &argsmax, bcc);
2038 if (argslen == argsmax)
2039 p_realloc(&args, ++argsmax);
2041 args[argslen++] = NULL;
2043 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2045 const char *e = mutt_strsysexit (i);
2047 e = mutt_strsysexit (i);
2048 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2053 if (stat (childout, &st) == 0 && st.st_size > 0)
2054 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2062 p_delete(&childout);
2067 if (i == (EX_OK & 0xff))
2069 else if (i == S_BKG)
2076 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2077 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2078 const char *msg, /* file containing message */
2080 { /* message contains 8bit chars */
2083 if (!option (OPTNEWSSEND))
2086 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2089 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2092 /* appends string 'b' to string 'a', and returns the pointer to the new
2094 char *mutt_append_string (char *a, const char *b)
2096 size_t la = m_strlen(a);
2098 p_realloc(&a, la + m_strlen(b) + 1);
2099 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2103 /* returns 1 if char `c' needs to be quoted to protect from shell
2104 interpretation when executing commands in a subshell */
2105 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2107 /* returns 1 if string `s' contains characters which could cause problems
2108 when used on a command line to execute a command */
2109 int mutt_needs_quote (const char *s)
2112 if (INVALID_CHAR (*s))
2119 /* Quote a string to prevent shell escapes when this string is used on the
2120 command line to send mail. */
2121 char *mutt_quote_string (const char *s)
2126 rlen = m_strlen(s) + 3;
2127 pr = r = p_new(char, rlen);
2130 if (INVALID_CHAR (*s)) {
2133 p_realloc(&r, ++rlen);
2144 /* For postponing (!final) do the necessary encodings only */
2145 void mutt_prepare_envelope (ENVELOPE * env, int final)
2147 char buffer[LONG_STRING];
2150 if (env->bcc && !(env->to || env->cc)) {
2151 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2152 * recipients if there is no To: or Cc: field, so attempt to suppress
2153 * it by using an empty To: field.
2155 env->to = rfc822_new_address ();
2157 env->to->next = rfc822_new_address ();
2160 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2163 env->to->mailbox = m_strdup(buffer);
2166 mutt_set_followup_to (env);
2168 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2169 env->message_id = mutt_gen_msgid ();
2172 /* Take care of 8-bit => 7-bit conversion. */
2173 rfc2047_encode_adrlist (env->to, "To");
2174 rfc2047_encode_adrlist (env->cc, "Cc");
2175 rfc2047_encode_adrlist (env->bcc, "Bcc");
2176 rfc2047_encode_adrlist (env->from, "From");
2177 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2178 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2182 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2185 rfc2047_encode_string (&env->subject);
2187 encode_headers (env->userhdrs);
2190 void mutt_unprepare_envelope (ENVELOPE * env)
2194 for (item = env->userhdrs; item; item = item->next)
2195 rfc2047_decode (&item->data);
2197 rfc822_free_address (&env->mail_followup_to);
2199 /* back conversions */
2200 rfc2047_decode_adrlist (env->to);
2201 rfc2047_decode_adrlist (env->cc);
2202 rfc2047_decode_adrlist (env->bcc);
2203 rfc2047_decode_adrlist (env->from);
2204 rfc2047_decode_adrlist (env->reply_to);
2205 rfc2047_decode (&env->subject);
2208 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2209 const char *resent_from, ADDRESS * env_from)
2213 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2214 MESSAGE *msg = NULL;
2217 /* Try to bounce each message out, aborting if we get any failures. */
2218 for (i = 0; i < Context->msgcount; i++)
2219 if (Context->hdrs[i]->tagged)
2221 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2226 /* If we failed to open a message, return with error */
2227 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2233 mutt_mktemp (tempfile);
2234 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2235 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2237 if (!option (OPTBOUNCEDELIVERED))
2238 ch_flags |= CH_WEED_DELIVERED;
2240 fseeko (fp, h->offset, 0);
2241 fprintf (f, "Resent-From: %s", resent_from);
2242 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2243 if (MsgIdFormat && *MsgIdFormat)
2244 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2245 fputs ("Resent-To: ", f);
2246 mutt_write_address_list (to, f, 11, 0);
2247 mutt_copy_header (fp, h, f, ch_flags, NULL);
2249 mutt_copy_bytes (fp, f, h->content->length);
2252 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2253 h->content->encoding == ENC8BIT);
2257 mx_close_message (&msg);
2262 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2265 const char *fqdn = mutt_fqdn (1);
2266 char resent_from[STRING];
2270 resent_from[0] = '\0';
2271 from = mutt_default_from ();
2274 rfc822_qualify (from, fqdn);
2276 rfc2047_encode_adrlist (from, "Resent-From");
2277 if (mutt_addrlist_to_idna (from, &err)) {
2278 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2281 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2284 unset_option (OPTNEWSSEND);
2287 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2289 rfc822_free_address (&from);
2295 /* given a list of addresses, return a list of unique addresses */
2296 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2298 ADDRESS *top = addr;
2299 ADDRESS **last = ⊤
2304 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2305 if (tmp->mailbox && addr->mailbox &&
2306 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2313 debug_print (2, ("Removing %s\n", addr->mailbox));
2318 rfc822_free_address (&addr);
2331 static void set_noconv_flags (BODY * b, short flag)
2333 for (; b; b = b->next) {
2334 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2335 set_noconv_flags (b->parts, flag);
2336 else if (b->type == TYPETEXT && b->noconv) {
2338 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2340 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2345 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2346 int post, char *fcc)
2350 char tempfile[_POSIX_PATH_MAX];
2351 FILE *tempfp = NULL;
2355 set_noconv_flags (hdr->content, 1);
2357 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2358 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2362 /* We need to add a Content-Length field to avoid problems where a line in
2363 * the message body begins with "From "
2365 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2366 mutt_mktemp (tempfile);
2367 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2368 mutt_perror (tempfile);
2369 mx_close_mailbox (&f, NULL);
2374 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2375 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2376 mx_close_mailbox (&f, NULL);
2380 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2381 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2383 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2386 /* (postponment) if this was a reply of some sort, <msgid> contians the
2387 * Message-ID: of message replied to. Save it using a special X-Mutt-
2388 * header so it can be picked up if the message is recalled at a later
2389 * point in time. This will allow the message to be marked as replied if
2390 * the same mailbox is still open.
2393 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2395 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2396 * it can be picked up when the message is recalled
2399 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2400 fprintf (msg->fp, "Status: RO\n");
2404 /* (postponment) if the mail is to be signed or encrypted, save this info */
2405 if ((WithCrypto & APPLICATION_PGP)
2406 && post && (hdr->security & APPLICATION_PGP)) {
2407 fputs ("X-Mutt-PGP: ", msg->fp);
2408 if (hdr->security & ENCRYPT)
2409 fputc ('E', msg->fp);
2410 if (hdr->security & SIGN) {
2411 fputc ('S', msg->fp);
2412 if (PgpSignAs && *PgpSignAs)
2413 fprintf (msg->fp, "<%s>", PgpSignAs);
2415 if (hdr->security & INLINE)
2416 fputc ('I', msg->fp);
2417 fputc ('\n', msg->fp);
2420 /* (postponment) if the mail is to be signed or encrypted, save this info */
2421 if ((WithCrypto & APPLICATION_SMIME)
2422 && post && (hdr->security & APPLICATION_SMIME)) {
2423 fputs ("X-Mutt-SMIME: ", msg->fp);
2424 if (hdr->security & ENCRYPT) {
2425 fputc ('E', msg->fp);
2426 if (SmimeCryptAlg && *SmimeCryptAlg)
2427 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2429 if (hdr->security & SIGN) {
2430 fputc ('S', msg->fp);
2431 if (SmimeDefaultKey && *SmimeDefaultKey)
2432 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2434 if (hdr->security & INLINE)
2435 fputc ('I', msg->fp);
2436 fputc ('\n', msg->fp);
2440 /* (postponement) if the mail is to be sent through a mixmaster
2441 * chain, save that information
2444 if (post && hdr->chain && hdr->chain) {
2447 fputs ("X-Mutt-Mix:", msg->fp);
2448 for (p = hdr->chain; p; p = p->next)
2449 fprintf (msg->fp, " %s", (char *) p->data);
2451 fputc ('\n', msg->fp);
2456 char sasha[LONG_STRING];
2459 mutt_write_mime_body (hdr->content, tempfp);
2461 /* make sure the last line ends with a newline. Emacs doesn't ensure
2462 * this will happen, and it can cause problems parsing the mailbox
2465 fseeko (tempfp, -1, 2);
2466 if (fgetc (tempfp) != '\n') {
2467 fseeko (tempfp, 0, 2);
2468 fputc ('\n', tempfp);
2472 if (ferror (tempfp)) {
2473 debug_print (1, ("%s: write failed.\n", tempfile));
2476 mx_commit_message (msg, &f); /* XXX - really? */
2477 mx_close_message (&msg);
2478 mx_close_mailbox (&f, NULL);
2482 /* count the number of lines */
2484 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2486 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2487 fprintf (msg->fp, "Lines: %d\n\n", lines);
2489 /* copy the body and clean up */
2491 r = mutt_copy_stream (tempfp, msg->fp);
2492 if (fclose (tempfp) != 0)
2494 /* if there was an error, leave the temp version */
2499 fputc ('\n', msg->fp); /* finish off the header */
2500 r = mutt_write_mime_body (hdr->content, msg->fp);
2503 if (mx_commit_message (msg, &f) != 0)
2505 mx_close_message (&msg);
2506 mx_close_mailbox (&f, NULL);
2509 set_noconv_flags (hdr->content, 0);