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>
24 #include "recvattach.h"
25 #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) && (szf == sze
939 /* get the content-type */
941 if ((p = strchr (ct, '/')) == NULL) {
942 /* malformed line, just skip it. */
947 for (q = p; *q && !ISSPACE (*q); q++);
949 str_substrcpy (subtype, p, q, sizeof (subtype));
951 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
952 m_strcpy(xtype, sizeof(xtype), ct);
965 if (type != TYPEOTHER || *xtype != '\0') {
967 str_replace (&att->subtype, subtype);
968 str_replace (&att->xtype, xtype);
974 void mutt_message_to_7bit (BODY * a, FILE * fp)
976 char temp[_POSIX_PATH_MAX];
982 if (!a->filename && fp)
984 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
985 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
990 if (stat (a->filename, &sb) == -1) {
991 mutt_perror ("stat");
994 a->length = sb.st_size;
998 if (!(fpout = safe_fopen (temp, "w+"))) {
999 mutt_perror ("fopen");
1003 fseeko (fpin, a->offset, 0);
1004 a->parts = mutt_parse_messageRFC822 (fpin, a);
1006 transform_to_7bit (a->parts, fpin);
1008 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1009 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1011 fputs ("MIME-Version: 1.0\n", fpout);
1012 mutt_write_mime_header (a->parts, fpout);
1013 fputc ('\n', fpout);
1014 mutt_write_mime_body (a->parts, fpout);
1026 a->encoding = ENC7BIT;
1027 a->d_filename = a->filename;
1028 if (a->filename && a->unlink)
1029 unlink (a->filename);
1030 a->filename = m_strdup(temp);
1032 if (stat (a->filename, &sb) == -1) {
1033 mutt_perror ("stat");
1036 a->length = sb.st_size;
1037 mutt_free_body (&a->parts);
1038 a->hdr->content = NULL;
1041 static void transform_to_7bit (BODY * a, FILE * fpin)
1043 char buff[_POSIX_PATH_MAX];
1048 for (; a; a = a->next) {
1049 if (a->type == TYPEMULTIPART) {
1050 if (a->encoding != ENC7BIT)
1051 a->encoding = ENC7BIT;
1053 transform_to_7bit (a->parts, fpin);
1055 else if (mutt_is_message_type (a->type, a->subtype)) {
1056 mutt_message_to_7bit (a, fpin);
1060 a->force_charset = 1;
1063 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1064 mutt_perror ("fopen");
1068 mutt_decode_attachment (a, &s);
1070 a->d_filename = a->filename;
1071 a->filename = m_strdup(buff);
1073 if (stat (a->filename, &sb) == -1) {
1074 mutt_perror ("stat");
1077 a->length = sb.st_size;
1079 mutt_update_encoding (a);
1080 if (a->encoding == ENC8BIT)
1081 a->encoding = ENCQUOTEDPRINTABLE;
1082 else if (a->encoding == ENCBINARY)
1083 a->encoding = ENCBASE64;
1088 /* determine which Content-Transfer-Encoding to use */
1089 static void mutt_set_encoding (BODY * b, CONTENT * info)
1091 char send_charset[SHORT_STRING];
1093 if (b->type == TYPETEXT) {
1095 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1096 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1097 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1098 b->encoding = ENCQUOTEDPRINTABLE;
1099 else if (info->hibin)
1100 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1102 b->encoding = ENC7BIT;
1104 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1105 if (info->lobin || info->hibin) {
1106 if (option (OPTALLOW8BIT) && !info->lobin)
1107 b->encoding = ENC8BIT;
1109 mutt_message_to_7bit (b, NULL);
1112 b->encoding = ENC7BIT;
1114 else if (b->type == TYPEAPPLICATION
1115 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1116 b->encoding = ENC7BIT;
1119 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1120 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1123 /* Determine which encoding is smaller */
1124 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1125 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1126 b->encoding = ENCBASE64;
1128 b->encoding = ENCQUOTEDPRINTABLE;
1132 b->encoding = ENC7BIT;
1136 void mutt_stamp_attachment (BODY * a)
1138 a->stamp = time (NULL);
1141 /* Get a body's character set */
1143 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1147 if (b && b->type != TYPETEXT)
1151 p = mutt_get_parameter ("charset", b->parameter);
1154 mutt_canonical_charset (d, dlen, NONULL (p));
1156 m_strcpy(d, dlen, "us-ascii");
1162 /* Assumes called from send mode where BODY->filename points to actual file */
1163 void mutt_update_encoding (BODY * a)
1166 char chsbuff[STRING];
1168 /* override noconv when it's us-ascii */
1169 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1172 if (!a->force_charset && !a->noconv)
1173 mutt_delete_parameter ("charset", &a->parameter);
1175 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1178 mutt_set_encoding (a, info);
1179 mutt_stamp_attachment (a);
1181 p_delete(&a->content);
1186 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1188 char buffer[LONG_STRING];
1191 int cmflags, chflags;
1192 int pgp = WithCrypto ? hdr->security : 0;
1195 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1196 (hdr->security & ENCRYPT)) {
1197 if (!crypt_valid_passphrase (hdr->security))
1202 mutt_mktemp (buffer);
1203 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1206 body = mutt_new_body ();
1207 body->type = TYPEMESSAGE;
1208 body->subtype = m_strdup("rfc822");
1209 body->filename = m_strdup(buffer);
1212 body->disposition = DISPINLINE;
1215 mutt_parse_mime_message (ctx, hdr);
1220 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1221 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1222 chflags |= CH_MIME | CH_TXTPLAIN;
1223 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1224 if ((WithCrypto & APPLICATION_PGP))
1226 if ((WithCrypto & APPLICATION_SMIME))
1227 pgp &= ~SMIMEENCRYPT;
1229 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1230 if ((WithCrypto & APPLICATION_PGP)
1231 && mutt_is_multipart_encrypted (hdr->content)) {
1232 chflags |= CH_MIME | CH_NONEWLINE;
1233 cmflags = M_CM_DECODE_PGP;
1236 else if ((WithCrypto & APPLICATION_PGP)
1237 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1238 chflags |= CH_MIME | CH_TXTPLAIN;
1239 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1242 else if ((WithCrypto & APPLICATION_SMIME)
1243 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1244 chflags |= CH_MIME | CH_TXTPLAIN;
1245 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1246 pgp &= ~SMIMEENCRYPT;
1250 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1255 body->hdr = mutt_new_header ();
1256 body->hdr->offset = 0;
1257 /* we don't need the user headers here */
1258 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1260 body->hdr->security = pgp;
1261 mutt_update_encoding (body);
1262 body->parts = body->hdr->content;
1269 BODY *mutt_make_file_attach (const char *path)
1274 att = mutt_new_body ();
1275 att->filename = m_strdup(path);
1277 /* Attempt to determine the appropriate content-type based on the filename
1284 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1285 path)) != TYPEOTHER || *xbuf != '\0') {
1287 att->subtype = m_strdup(buf);
1288 att->xtype = m_strdup(xbuf);
1293 mutt_lookup_mime_type (att, path);
1297 if ((info = mutt_get_content_info (path, att)) == NULL) {
1298 mutt_free_body (&att);
1302 if (!att->subtype) {
1303 if (info->lobin == 0
1304 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1306 * Statistically speaking, there should be more than 10% "lobin"
1307 * chars if this is really a binary file...
1309 att->type = TYPETEXT;
1310 att->subtype = m_strdup("plain");
1313 att->type = TYPEAPPLICATION;
1314 att->subtype = m_strdup("octet-stream");
1318 mutt_update_encoding (att);
1322 static int get_toplevel_encoding (BODY * a)
1326 for (; a; a = a->next) {
1327 if (a->encoding == ENCBINARY)
1329 else if (a->encoding == ENC8BIT)
1336 BODY *mutt_make_multipart (BODY * b)
1340 new = mutt_new_body ();
1341 new->type = TYPEMULTIPART;
1342 new->subtype = m_strdup("mixed");
1343 new->encoding = get_toplevel_encoding (b);
1344 mutt_generate_boundary (&new->parameter);
1346 new->disposition = DISPINLINE;
1352 /* remove the multipart body if it exists */
1353 BODY *mutt_remove_multipart (BODY * b)
1361 mutt_free_body (&t);
1366 char *mutt_make_date (char *s, size_t len)
1368 time_t t = time (NULL);
1369 struct tm *l = localtime (&t);
1370 time_t tz = mutt_local_tz (t);
1374 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1375 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1376 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1377 (int) tz / 60, (int) abs (tz) % 60);
1381 /* wrapper around mutt_write_address() so we can handle very large
1382 recipient lists without needing a huge temporary buffer in memory */
1383 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1387 char buf[LONG_STRING];
1395 rfc822_write_address (buf, sizeof (buf), adr, display);
1396 len = m_strlen(buf);
1397 if (count && linelen + len > 74) {
1399 linelen = len + 8; /* tab is usually about 8 spaces... */
1402 if (count && adr->mailbox) {
1410 if (!adr->group && adr->next && adr->next->mailbox) {
1420 /* arbitrary number of elements to grow the array by */
1425 /* need to write the list in reverse because they are stored in reverse order
1426 * when parsed to speed up threading
1428 void mutt_write_references (LIST * r, FILE * f)
1431 int refcnt = 0, refmax = 0;
1433 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1434 if (refcnt == refmax)
1435 p_realloc(&ref, refmax += REF_INC);
1439 while (refcnt-- > 0) {
1441 fputs (ref[refcnt]->data, f);
1447 /* Note: all RFC2047 encoding should be done outside of this routine, except
1448 * for the "real name." This will allow this routine to be used more than
1449 * once, if necessary.
1451 * Likewise, all IDN processing should happen outside of this routine.
1453 * mode == 1 => "lite" mode (used for edit_hdrs)
1454 * mode == 0 => normal mode. write full header + MIME headers
1455 * mode == -1 => write just the envelope info (used for postponing messages)
1457 * privacy != 0 => will omit any headers which may identify the user.
1458 * Output generated is suitable for being sent through
1459 * anonymous remailer chains.
1463 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1464 int mode, int privacy)
1466 char buffer[LONG_STRING];
1468 LIST *tmp = env->userhdrs;
1469 int has_agent = 0; /* user defined user-agent header field exists */
1470 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1473 if (!option (OPTNEWSSEND))
1475 if (mode == 0 && !privacy)
1476 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1478 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1480 /* OPTUSEFROM is not consulted here so that we can still write a From:
1481 * field if the user sets it with the `my_hdr' command
1483 if (env->from && !privacy) {
1485 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1486 fprintf (fp, "From: %s\n", buffer);
1491 mutt_write_address_list (env->to, fp, 4, 0);
1495 if (!option (OPTNEWSSEND))
1497 if (EDIT_HEADER("To:"))
1498 fputs ("To:\n", fp);
1502 mutt_write_address_list (env->cc, fp, 4, 0);
1506 if (!option (OPTNEWSSEND))
1508 if (EDIT_HEADER("Cc:"))
1509 fputs ("Cc:\n", fp);
1512 if (mode != 0 || option (OPTWRITEBCC)) {
1513 fputs ("Bcc: ", fp);
1514 mutt_write_address_list (env->bcc, fp, 5, 0);
1519 if (!option (OPTNEWSSEND))
1521 if (EDIT_HEADER("Bcc:"))
1522 fputs ("Bcc:\n", fp);
1525 if (env->newsgroups)
1526 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1527 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1528 fputs ("Newsgroups:\n", fp);
1530 if (env->followup_to)
1531 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1532 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1533 fputs ("Followup-To:\n", fp);
1535 if (env->x_comment_to)
1536 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1537 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1538 EDIT_HEADER("X-Comment-To:"))
1539 fputs ("X-Comment-To:\n", fp);
1543 fprintf (fp, "Subject: %s\n", env->subject);
1544 else if (mode == 1 && EDIT_HEADER("Subject:"))
1545 fputs ("Subject:\n", fp);
1547 /* save message id if the user has set it */
1548 if (env->message_id && !privacy)
1549 fprintf (fp, "Message-ID: %s\n", env->message_id);
1551 if (env->reply_to) {
1552 fputs ("Reply-To: ", fp);
1553 mutt_write_address_list (env->reply_to, fp, 10, 0);
1555 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1556 fputs ("Reply-To:\n", fp);
1558 if (env->mail_followup_to)
1560 if (!option (OPTNEWSSEND))
1563 fputs ("Mail-Followup-To: ", fp);
1564 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1568 if (env->references) {
1569 fputs ("References:", fp);
1570 mutt_write_references (env->references, fp);
1574 /* Add the MIME headers */
1575 fputs ("MIME-Version: 1.0\n", fp);
1576 mutt_write_mime_header (attach, fp);
1579 if (env->in_reply_to) {
1580 fputs ("In-Reply-To:", fp);
1581 mutt_write_references (env->in_reply_to, fp);
1587 /* Add any user defined headers */
1588 for (; tmp; tmp = tmp->next) {
1589 if ((p = strchr (tmp->data, ':'))) {
1590 p = vskipspaces(p + 1);
1592 continue; /* don't emit empty fields. */
1594 /* check to see if the user has overridden the user-agent field */
1595 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1601 fputs (tmp->data, fp);
1606 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1609 if (OperatingSystem != NULL) {
1610 os = OperatingSystem;
1613 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1615 /* Add a vanity header */
1616 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1619 list_del (&hdrs, (list_del_t*)xmemfree);
1621 return (ferror (fp) == 0 ? 0 : -1);
1624 static void encode_headers (LIST * h)
1630 for (; h; h = h->next) {
1631 if (!(p = strchr (h->data, ':')))
1635 p = vskipspaces(p + 1);
1641 rfc2047_encode_string (&tmp);
1642 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1644 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1650 const char *mutt_fqdn (short may_hide_host)
1654 if (Fqdn && Fqdn[0] != '@') {
1657 if (may_hide_host && option (OPTHIDDENHOST)) {
1658 if ((p = strchr (Fqdn, '.')))
1661 /* sanity check: don't hide the host if
1662 * the fqdn is something like detebe.org.
1665 if (!p || !(q = strchr (p, '.')))
1673 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1674 static char mutt_normalized_char(char c)
1676 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1679 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1681 #define APPEND_FMT(fmt, arg) \
1683 int snlen = snprintf(buf, len, fmt, arg); \
1688 #define APPEND_BYTE(c) \
1705 APPEND_BYTE(mutt_normalized_char(c));
1713 APPEND_FMT("%02d", tm->tm_mday);
1716 APPEND_FMT("%02d", tm->tm_hour);
1719 APPEND_FMT("%02d", tm->tm_mon + 1);
1722 APPEND_FMT("%02d", tm->tm_min);
1725 APPEND_FMT("%lo", (unsigned long)now);
1728 APPEND_FMT("%u", (unsigned int)getpid());
1731 APPEND_FMT("%c", MsgIdPfx);
1732 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1735 APPEND_FMT("%u", (unsigned int)rand());
1738 APPEND_FMT("%x", (unsigned int)rand());
1741 APPEND_FMT("%02d", tm->tm_sec);
1744 APPEND_FMT("%u", (unsigned int) now);
1747 APPEND_FMT("%x", (unsigned int) now);
1749 case 'Y': /* this will break in the year 10000 ;-) */
1750 APPEND_FMT("%04d", tm->tm_year + 1900);
1755 default: /* invalid formats are replaced by '.' */
1757 m_strncat(buf, len, ".", 1);
1767 char *mutt_gen_msgid (void)
1769 char buf[SHORT_STRING];
1770 char localpart[SHORT_STRING];
1771 unsigned int localpart_length;
1774 if (!(fqdn = mutt_fqdn (0)))
1775 fqdn = NONULL (Hostname);
1777 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1779 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1781 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1782 return (m_strdup(buf));
1785 static RETSIGTYPE alarm_handler (int sig)
1790 /* invoke sendmail in a subshell
1791 path (in) path to program to execute
1792 args (in) arguments to pass to program
1793 msg (in) temp file containing message to send
1794 tempfile (out) if sendmail is put in the background, this points
1795 to the temporary file containing the stdout of the
1798 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1804 mutt_block_signals_system ();
1807 /* we also don't want to be stopped right now */
1808 sigaddset (&set, SIGTSTP);
1809 sigprocmask (SIG_BLOCK, &set, NULL);
1811 if (SendmailWait >= 0) {
1812 char tmp[_POSIX_PATH_MAX];
1815 *tempfile = m_strdup(tmp);
1818 if ((pid = fork ()) == 0) {
1819 struct sigaction act, oldalrm;
1821 /* save parent's ID before setsid() */
1824 /* we want the delivery to continue even after the main process dies,
1825 * so we put ourselves into another session right away
1829 /* next we close all open files */
1830 #if defined(OPEN_MAX)
1831 for (fd = 0; fd < OPEN_MAX; fd++)
1833 #elif defined(_POSIX_OPEN_MAX)
1834 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1842 /* now the second fork() */
1843 if ((pid = fork ()) == 0) {
1844 /* "msg" will be opened as stdin */
1845 if (open (msg, O_RDONLY, 0) < 0) {
1851 if (SendmailWait >= 0) {
1852 /* *tempfile will be opened as stdout */
1853 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1856 /* redirect stderr to *tempfile too */
1861 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1863 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1870 else if (pid == -1) {
1876 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1877 * SendmailWait = 0: wait forever
1878 * SendmailWait < 0: don't wait
1880 if (SendmailWait > 0) {
1882 act.sa_handler = alarm_handler;
1884 /* need to make sure waitpid() is interrupted on SIGALRM */
1885 act.sa_flags = SA_INTERRUPT;
1889 sigemptyset (&act.sa_mask);
1890 sigaction (SIGALRM, &act, &oldalrm);
1891 alarm (SendmailWait);
1893 else if (SendmailWait < 0)
1894 _exit (0xff & EX_OK);
1896 if (waitpid (pid, &st, 0) > 0) {
1897 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1898 if (SendmailWait && st == (0xff & EX_OK)) {
1899 unlink (*tempfile); /* no longer needed */
1904 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1905 if (SendmailWait > 0) {
1911 /* reset alarm; not really needed, but... */
1913 sigaction (SIGALRM, &oldalrm, NULL);
1915 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1916 /* the parent is already dead */
1924 sigprocmask (SIG_UNBLOCK, &set, NULL);
1926 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1927 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1929 st = S_ERR; /* error */
1931 mutt_unblock_signals_system (1);
1936 static const char **
1937 add_args(const char **args, size_t *argslen, size_t *argsmax, ADDRESS * addr)
1939 for (; addr; addr = addr->next) {
1940 /* weed out group mailboxes, since those are for display only */
1941 if (addr->mailbox && !addr->group) {
1942 if (*argslen == *argsmax)
1943 p_realloc(&args, *argsmax += 5);
1944 args[(*argslen)++] = addr->mailbox;
1950 static const char **
1951 add_option(const char **args, size_t *argslen, size_t *argsmax, const char *s)
1953 if (*argslen == *argsmax) {
1954 p_realloc(&args, *argsmax += 5);
1956 args[(*argslen)++] = s;
1960 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
1961 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
1962 const char *msg, /* file containing message */
1964 { /* message contains 8bit chars */
1965 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1966 const char **args = NULL;
1967 size_t argslen = 0, argsmax = 0;
1971 if (option (OPTNEWSSEND)) {
1972 char cmd[LONG_STRING];
1974 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1977 i = nntp_post (msg);
1986 s = m_strdup(Sendmail);
1990 while ((ps = strtok (ps, " "))) {
1991 if (argslen == argsmax)
1992 p_realloc(&args, argsmax += 5);
1995 args[argslen++] = ps;
1997 path = m_strdup(ps);
1998 ps = strrchr (ps, '/');
2003 args[argslen++] = ps;
2010 if (!option (OPTNEWSSEND)) {
2012 if (eightbit && option (OPTUSE8BITMIME))
2013 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
2015 if (option (OPTENVFROM)) {
2019 else if (from && !from->next)
2022 args = add_option (args, &argslen, &argsmax, "-f");
2023 args = add_args (args, &argslen, &argsmax, f);
2027 args = add_option (args, &argslen, &argsmax, "-N");
2028 args = add_option (args, &argslen, &argsmax, DsnNotify);
2031 args = add_option (args, &argslen, &argsmax, "-R");
2032 args = add_option (args, &argslen, &argsmax, DsnReturn);
2034 args = add_option (args, &argslen, &argsmax, "--");
2035 args = add_args (args, &argslen, &argsmax, to);
2036 args = add_args (args, &argslen, &argsmax, cc);
2037 args = add_args (args, &argslen, &argsmax, bcc);
2042 if (argslen == argsmax)
2043 p_realloc(&args, ++argsmax);
2045 args[argslen++] = NULL;
2047 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2049 const char *e = mutt_strsysexit (i);
2051 e = mutt_strsysexit (i);
2052 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2057 if (stat (childout, &st) == 0 && st.st_size > 0)
2058 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2066 p_delete(&childout);
2071 if (i == (EX_OK & 0xff))
2073 else if (i == S_BKG)
2080 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2081 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2082 const char *msg, /* file containing message */
2084 { /* message contains 8bit chars */
2087 if (!option (OPTNEWSSEND))
2090 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2093 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2096 /* appends string 'b' to string 'a', and returns the pointer to the new
2098 char *mutt_append_string (char *a, const char *b)
2100 size_t la = m_strlen(a);
2102 p_realloc(&a, la + m_strlen(b) + 1);
2103 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2107 /* returns 1 if char `c' needs to be quoted to protect from shell
2108 interpretation when executing commands in a subshell */
2109 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2111 /* returns 1 if string `s' contains characters which could cause problems
2112 when used on a command line to execute a command */
2113 int mutt_needs_quote (const char *s)
2116 if (INVALID_CHAR (*s))
2123 /* Quote a string to prevent shell escapes when this string is used on the
2124 command line to send mail. */
2125 char *mutt_quote_string (const char *s)
2130 rlen = m_strlen(s) + 3;
2131 pr = r = p_new(char, rlen);
2134 if (INVALID_CHAR (*s)) {
2137 p_realloc(&r, ++rlen);
2148 /* For postponing (!final) do the necessary encodings only */
2149 void mutt_prepare_envelope (ENVELOPE * env, int final)
2151 char buffer[LONG_STRING];
2154 if (env->bcc && !(env->to || env->cc)) {
2155 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2156 * recipients if there is no To: or Cc: field, so attempt to suppress
2157 * it by using an empty To: field.
2159 env->to = rfc822_new_address ();
2161 env->to->next = rfc822_new_address ();
2164 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2167 env->to->mailbox = m_strdup(buffer);
2170 mutt_set_followup_to (env);
2172 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2173 env->message_id = mutt_gen_msgid ();
2176 /* Take care of 8-bit => 7-bit conversion. */
2177 rfc2047_encode_adrlist (env->to, "To");
2178 rfc2047_encode_adrlist (env->cc, "Cc");
2179 rfc2047_encode_adrlist (env->bcc, "Bcc");
2180 rfc2047_encode_adrlist (env->from, "From");
2181 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2182 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2186 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2189 rfc2047_encode_string (&env->subject);
2191 encode_headers (env->userhdrs);
2194 void mutt_unprepare_envelope (ENVELOPE * env)
2198 for (item = env->userhdrs; item; item = item->next)
2199 rfc2047_decode (&item->data);
2201 rfc822_free_address (&env->mail_followup_to);
2203 /* back conversions */
2204 rfc2047_decode_adrlist (env->to);
2205 rfc2047_decode_adrlist (env->cc);
2206 rfc2047_decode_adrlist (env->bcc);
2207 rfc2047_decode_adrlist (env->from);
2208 rfc2047_decode_adrlist (env->reply_to);
2209 rfc2047_decode (&env->subject);
2212 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2213 const char *resent_from, ADDRESS * env_from)
2217 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2218 MESSAGE *msg = NULL;
2221 /* Try to bounce each message out, aborting if we get any failures. */
2222 for (i = 0; i < Context->msgcount; i++)
2223 if (Context->hdrs[i]->tagged)
2225 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2230 /* If we failed to open a message, return with error */
2231 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2237 mutt_mktemp (tempfile);
2238 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2239 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2241 if (!option (OPTBOUNCEDELIVERED))
2242 ch_flags |= CH_WEED_DELIVERED;
2244 fseeko (fp, h->offset, 0);
2245 fprintf (f, "Resent-From: %s", resent_from);
2246 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2247 if (MsgIdFormat && *MsgIdFormat)
2248 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2249 fputs ("Resent-To: ", f);
2250 mutt_write_address_list (to, f, 11, 0);
2251 mutt_copy_header (fp, h, f, ch_flags, NULL);
2253 mutt_copy_bytes (fp, f, h->content->length);
2256 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2257 h->content->encoding == ENC8BIT);
2261 mx_close_message (&msg);
2266 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2269 const char *fqdn = mutt_fqdn (1);
2270 char resent_from[STRING];
2274 resent_from[0] = '\0';
2275 from = mutt_default_from ();
2278 rfc822_qualify (from, fqdn);
2280 rfc2047_encode_adrlist (from, "Resent-From");
2281 if (mutt_addrlist_to_idna (from, &err)) {
2282 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2285 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2288 unset_option (OPTNEWSSEND);
2291 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2293 rfc822_free_address (&from);
2299 /* given a list of addresses, return a list of unique addresses */
2300 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2302 ADDRESS *top = addr;
2303 ADDRESS **last = ⊤
2308 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2309 if (tmp->mailbox && addr->mailbox &&
2310 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2317 debug_print (2, ("Removing %s\n", addr->mailbox));
2322 rfc822_free_address (&addr);
2335 static void set_noconv_flags (BODY * b, short flag)
2337 for (; b; b = b->next) {
2338 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2339 set_noconv_flags (b->parts, flag);
2340 else if (b->type == TYPETEXT && b->noconv) {
2342 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2344 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2349 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2350 int post, char *fcc)
2354 char tempfile[_POSIX_PATH_MAX];
2355 FILE *tempfp = NULL;
2359 set_noconv_flags (hdr->content, 1);
2361 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2362 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2366 /* We need to add a Content-Length field to avoid problems where a line in
2367 * the message body begins with "From "
2369 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2370 mutt_mktemp (tempfile);
2371 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2372 mutt_perror (tempfile);
2373 mx_close_mailbox (&f, NULL);
2378 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2379 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2380 mx_close_mailbox (&f, NULL);
2384 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2385 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2387 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2390 /* (postponment) if this was a reply of some sort, <msgid> contians the
2391 * Message-ID: of message replied to. Save it using a special X-Mutt-
2392 * header so it can be picked up if the message is recalled at a later
2393 * point in time. This will allow the message to be marked as replied if
2394 * the same mailbox is still open.
2397 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2399 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2400 * it can be picked up when the message is recalled
2403 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2404 fprintf (msg->fp, "Status: RO\n");
2408 /* (postponment) if the mail is to be signed or encrypted, save this info */
2409 if ((WithCrypto & APPLICATION_PGP)
2410 && post && (hdr->security & APPLICATION_PGP)) {
2411 fputs ("X-Mutt-PGP: ", msg->fp);
2412 if (hdr->security & ENCRYPT)
2413 fputc ('E', msg->fp);
2414 if (hdr->security & SIGN) {
2415 fputc ('S', msg->fp);
2416 if (PgpSignAs && *PgpSignAs)
2417 fprintf (msg->fp, "<%s>", PgpSignAs);
2419 if (hdr->security & INLINE)
2420 fputc ('I', msg->fp);
2421 fputc ('\n', msg->fp);
2424 /* (postponment) if the mail is to be signed or encrypted, save this info */
2425 if ((WithCrypto & APPLICATION_SMIME)
2426 && post && (hdr->security & APPLICATION_SMIME)) {
2427 fputs ("X-Mutt-SMIME: ", msg->fp);
2428 if (hdr->security & ENCRYPT) {
2429 fputc ('E', msg->fp);
2430 if (SmimeCryptAlg && *SmimeCryptAlg)
2431 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2433 if (hdr->security & SIGN) {
2434 fputc ('S', msg->fp);
2435 if (SmimeDefaultKey && *SmimeDefaultKey)
2436 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2438 if (hdr->security & INLINE)
2439 fputc ('I', msg->fp);
2440 fputc ('\n', msg->fp);
2444 /* (postponement) if the mail is to be sent through a mixmaster
2445 * chain, save that information
2448 if (post && hdr->chain && hdr->chain) {
2451 fputs ("X-Mutt-Mix:", msg->fp);
2452 for (p = hdr->chain; p; p = p->next)
2453 fprintf (msg->fp, " %s", (char *) p->data);
2455 fputc ('\n', msg->fp);
2460 char sasha[LONG_STRING];
2463 mutt_write_mime_body (hdr->content, tempfp);
2465 /* make sure the last line ends with a newline. Emacs doesn't ensure
2466 * this will happen, and it can cause problems parsing the mailbox
2469 fseeko (tempfp, -1, 2);
2470 if (fgetc (tempfp) != '\n') {
2471 fseeko (tempfp, 0, 2);
2472 fputc ('\n', tempfp);
2476 if (ferror (tempfp)) {
2477 debug_print (1, ("%s: write failed.\n", tempfile));
2480 mx_commit_message (msg, &f); /* XXX - really? */
2481 mx_close_message (&msg);
2482 mx_close_mailbox (&f, NULL);
2486 /* count the number of lines */
2488 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2490 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2491 fprintf (msg->fp, "Lines: %d\n\n", lines);
2493 /* copy the body and clean up */
2495 r = mutt_copy_stream (tempfp, msg->fp);
2496 if (fclose (tempfp) != 0)
2498 /* if there was an error, leave the temp version */
2503 fputc ('\n', msg->fp); /* finish off the header */
2504 r = mutt_write_mime_body (hdr->content, msg->fp);
2507 if (mx_commit_message (msg, &f) != 0)
2509 mx_close_message (&msg);
2510 mx_close_mailbox (&f, NULL);
2513 set_noconv_flags (hdr->content, 0);