2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
5 * This file is part of mutt-ng, see http://www.muttng.org/.
6 * It's licensed under the GNU General Public License,
7 * please see the file GPL in the top level source directory.
25 #include <sys/utsname.h>
27 #include <lib-lib/mem.h>
28 #include <lib-lib/ascii.h>
29 #include <lib-lib/str.h>
30 #include <lib-lib/macros.h>
31 #include <lib-lib/file.h>
32 #include <lib-lib/debug.h>
34 #include <lib-mime/mime.h>
36 #include <lib-ui/curses.h>
40 #include "recvattach.h"
45 #include <lib-crypt/crypt.h>
46 #include "mutt_idna.h"
49 # include "mutt_libesmtp.h"
50 #endif /* USE_LIBESMTP */
56 #ifdef HAVE_SYSEXITS_H
58 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
62 /* If you are debugging this file, comment out the following line. */
71 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
73 static char MsgIdPfx = 'A';
75 static void transform_to_7bit (BODY * a, FILE * fpin);
77 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
80 char line[77], savechar;
82 while ((c = fgetconv (fc)) != EOF) {
83 /* Wrap the line if needed. */
84 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
85 /* If the last character is "quoted", then be sure to move all three
86 * characters to the next line. Otherwise, just move the last
89 if (line[linelen - 3] == '=') {
90 line[linelen - 3] = 0;
95 line[1] = line[linelen - 2];
96 line[2] = line[linelen - 1];
100 savechar = line[linelen - 1];
101 line[linelen - 1] = '=';
110 /* Escape lines that begin with/only contain "the message separator". */
111 if (linelen == 4 && !m_strncmp("From", line, 4)) {
112 m_strcpy(line, sizeof(line), "=46rom");
115 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
116 m_strcpy(line, sizeof(line), "=66rom");
119 else if (linelen == 1 && line[0] == '.') {
120 m_strcpy(line, sizeof(line), "=2E");
125 if (c == '\n' && istext) {
126 /* Check to make sure there is no trailing space on this line. */
128 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
130 sprintf (line + linelen - 1, "=%2.2X",
131 (unsigned char) line[linelen - 1]);
135 int savechar = line[linelen - 1];
137 line[linelen - 1] = '=';
140 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
150 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
151 /* Check to make sure there is enough room for the quoted character.
152 * If not, wrap to the next line.
155 line[linelen++] = '=';
161 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
165 /* Don't worry about wrapping the line here. That will happen during
166 * the next iteration when I'll also know what the next character is.
172 /* Take care of anything left in the buffer */
174 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
175 /* take care of trailing whitespace */
177 sprintf (line + linelen - 1, "=%2.2X",
178 (unsigned char) line[linelen - 1]);
180 savechar = line[linelen - 1];
181 line[linelen - 1] = '=';
185 sprintf (line, "=%2.2X", (unsigned char) savechar);
194 static char b64_buffer[3];
195 static short b64_num;
196 static short b64_linelen;
198 static void b64_flush (FILE * fout)
205 if (b64_linelen >= 72) {
210 for (i = b64_num; i < 3; i++)
211 b64_buffer[i] = '\0';
213 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
216 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
221 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
225 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
230 while (b64_linelen % 4) {
239 static void b64_putc (char c, FILE * fout)
244 b64_buffer[b64_num++] = c;
248 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
252 b64_num = b64_linelen = 0;
254 while ((ch = fgetconv (fc)) != EOF) {
255 if (istext && ch == '\n' && ch1 != '\r')
256 b64_putc ('\r', fout);
264 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
268 while ((ch = fgetconv (fc)) != EOF)
273 int mutt_write_mime_header (BODY * a, FILE * f)
283 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
286 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
288 for (p = a->parameter; p; p = p->next) {
297 tmp = m_strdup(p->value);
298 encode = rfc2231_encode_string (&tmp);
299 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
301 /* Dirty hack to make messages readable by Outlook Express
302 * for the Mac: force quotes around the boundary parameter
303 * even when they aren't needed.
306 if (!ascii_strcasecmp (p->attribute, "boundary")
307 && !strcmp (buffer, tmp))
308 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
312 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
314 if (len + tmplen + 2 > 76) {
323 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
331 fprintf (f, "Content-Description: %s\n", a->description);
333 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
336 if (!(fn = a->d_filename))
342 /* Strip off the leading path... */
343 if ((t = strrchr (fn, '/')))
350 encode = rfc2231_encode_string (&tmp);
351 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
353 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
359 if (a->encoding != ENC7BIT)
360 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
362 /* Do NOT add the terminator here!!! */
363 return (ferror (f) ? -1 : 0);
366 # define write_as_text_part(a) (mutt_is_text_part(a) || mutt_is_application_pgp(a))
368 int mutt_write_mime_body (BODY * a, FILE * f)
370 char *p, boundary[SHORT_STRING];
371 char send_charset[SHORT_STRING];
376 if (a->type == TYPEMULTIPART) {
377 /* First, find the boundary to use */
378 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
379 debug_print (1, ("no boundary parameter found!\n"));
380 mutt_error _("No boundary parameter found! [report this error]");
384 m_strcpy(boundary, sizeof(boundary), p);
386 for (t = a->parts; t; t = t->next) {
387 fprintf (f, "\n--%s\n", boundary);
388 if (mutt_write_mime_header (t, f) == -1)
391 if (mutt_write_mime_body (t, f) == -1)
394 fprintf (f, "\n--%s--\n", boundary);
395 return (ferror (f) ? -1 : 0);
398 /* This is pretty gross, but it's the best solution for now... */
399 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
400 fputs ("Version: 1\n", f);
404 if ((fpin = fopen (a->filename, "r")) == NULL) {
405 debug_print (1, ("%s no longer exists!\n", a->filename));
406 mutt_error (_("%s no longer exists!"), a->filename);
410 if (a->type == TYPETEXT && (!a->noconv))
411 fc = fgetconv_open (fpin, a->file_charset,
412 mutt_get_body_charset (send_charset,
413 sizeof (send_charset), a), 0);
415 fc = fgetconv_open (fpin, 0, 0, 0);
417 if (a->encoding == ENCQUOTEDPRINTABLE)
418 encode_quoted (fc, f, write_as_text_part (a));
419 else if (a->encoding == ENCBASE64)
420 encode_base64 (fc, f, write_as_text_part (a));
421 else if (a->type == TYPETEXT && (!a->noconv))
422 encode_8bit (fc, f, write_as_text_part (a));
424 mutt_copy_stream (fpin, f);
426 fgetconv_close (&fc);
429 return (ferror (f) ? -1 : 0);
432 #undef write_as_text_part
434 #define BOUNDARYLEN 16
435 void mutt_generate_boundary (PARAMETER ** parm)
437 char rs[BOUNDARYLEN + 1];
442 for (i = 0; i < BOUNDARYLEN; i++)
443 *p++ = __m_b64chars[LRAND() % sizeof(__m_b64chars)];
446 mutt_set_parameter ("boundary", rs, parm);
458 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
462 int whitespace = s->whitespace;
464 int linelen = s->linelen;
465 int was_cr = s->was_cr;
467 if (!d) { /* This signals EOF */
470 if (linelen > info->linemax)
471 info->linemax = linelen;
476 for (; dlen; d++, dlen--) {
489 if (linelen > info->linemax)
490 info->linemax = linelen;
505 if (linelen > info->linemax)
506 info->linemax = linelen;
511 else if (ch == '\r') {
519 else if (ch == '\t' || ch == '\f') {
523 else if (ch < 32 || ch == 127)
527 if ((ch == 'F') || (ch == 'f'))
537 if (linelen == 2 && ch != 'r')
539 else if (linelen == 3 && ch != 'o')
541 else if (linelen == 4) {
554 if (ch != ' ' && ch != '\t')
559 s->whitespace = whitespace;
561 s->linelen = linelen;
566 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
567 #define BUGGY_ICONV 1
570 * Find the best charset conversion of the file from fromcode into one
571 * of the tocodes. If successful, set *tocode and CONTENT *info and
572 * return the number of characters converted inexactly. If no
573 * conversion was possible, return -1.
575 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
576 * which would otherwise prevent us from knowing the number of inexact
577 * conversions. Where the candidate target charset is UTF-8 we avoid
578 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
579 * fails with some libraries.
581 * We assume that the output from iconv is never more than 4 times as
582 * long as the input for any pair of charsets we might be interested
585 static size_t convert_file_to (FILE * file, const char *fromcode,
586 int ncodes, const char **tocodes,
587 int *tocode, CONTENT * info)
591 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
594 size_t ibl, obl, ubl, ubl1, n, ret;
597 CONTENT_STATE *states;
600 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
601 if (cd1 == (iconv_t) (-1))
604 cd = p_new(iconv_t, ncodes);
605 score = p_new(size_t, ncodes);
606 states = p_new(CONTENT_STATE, ncodes);
607 infos = p_new(CONTENT, ncodes);
609 for (i = 0; i < ncodes; i++)
610 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
611 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
613 /* Special case for conversion to UTF-8 */
614 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
620 /* Try to fill input buffer */
621 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
624 /* Convert to UTF-8 */
626 ob = bufu, obl = sizeof (bufu);
627 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
628 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
629 if (n == (size_t) (-1) &&
630 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
631 assert (errno == EILSEQ ||
632 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
638 /* Convert from UTF-8 */
639 for (i = 0; i < ncodes; i++)
640 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
641 ub = bufu, ubl = ubl1;
642 ob = bufo, obl = sizeof (bufo);
643 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
644 if (n == (size_t) (-1)) {
645 assert (errno == E2BIG ||
646 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
647 score[i] = (size_t) (-1);
651 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
654 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
655 /* Special case for conversion to UTF-8 */
656 update_content_info (&infos[i], &states[i], bufu, ubl1);
659 /* Save unused input */
660 memmove (bufi, ib, ibl);
661 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
668 /* Find best score */
670 for (i = 0; i < ncodes; i++) {
671 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
672 /* Special case for conversion to UTF-8 */
677 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
679 else if (ret == (size_t) (-1) || score[i] < ret) {
686 if (ret != (size_t) (-1)) {
687 memcpy (info, &infos[*tocode], sizeof (CONTENT));
688 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
692 for (i = 0; i < ncodes; i++)
693 if (cd[i] != (iconv_t) (-1))
705 #endif /* !HAVE_ICONV */
709 * Find the first of the fromcodes that gives a valid conversion and
710 * the best charset conversion of the file into one of the tocodes. If
711 * successful, set *fromcode and *tocode to dynamically allocated
712 * strings, set CONTENT *info, and return the number of characters
713 * converted inexactly. If no conversion was possible, return -1.
715 * Both fromcodes and tocodes may be colon-separated lists of charsets.
716 * However, if fromcode is zero then fromcodes is assumed to be the
717 * name of a single charset even if it contains a colon.
719 static size_t convert_file_from_to (FILE * file,
720 const char *fromcodes,
721 const char *tocodes, char **fromcode,
722 char **tocode, CONTENT * info)
730 /* Count the tocodes */
732 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
733 if ((c1 = strchr (c, ':')) == c)
739 tcode = p_new(char *, ncodes);
740 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
741 if ((c1 = strchr (c, ':')) == c)
743 tcode[i] = m_substrdup(c, c1);
748 /* Try each fromcode in turn */
749 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
750 if ((c1 = strchr (c, ':')) == c)
752 fcode = m_substrdup(c, c1);
754 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
756 if (ret != (size_t) (-1)) {
766 /* There is only one fromcode */
767 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
769 if (ret != (size_t) (-1)) {
776 for (i = 0; i < ncodes; i++)
785 * Analyze the contents of a file to determine which MIME encoding to use.
786 * Also set the body charset, sometimes, or not.
788 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
793 char *fromcode = NULL;
804 if (stat (fname, &sb) == -1) {
805 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
809 if (!S_ISREG (sb.st_mode)) {
810 mutt_error (_("%s isn't a regular file."), fname);
814 if ((fp = fopen (fname, "r")) == NULL) {
815 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
819 info = p_new(CONTENT, 1);
822 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
823 char *chs = mutt_get_parameter ("charset", b->parameter);
824 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
825 FileCharset : Charset) : Charset;
826 if (Charset && (chs || SendCharset) &&
827 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
828 &fromcode, &tocode, info) != (size_t) (-1)) {
830 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
831 mutt_set_parameter ("charset", chsbuf, &b->parameter);
833 b->file_charset = fromcode;
841 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
842 update_content_info (info, &state, buffer, r);
843 update_content_info (info, &state, 0, 0);
847 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
848 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
850 && !mutt_is_us_ascii (Charset) ? Charset :
851 "unknown-8bit"), &b->parameter);
856 /* Given a file with path ``s'', see if there is a registered MIME type.
857 * returns the major MIME type, and copies the subtype to ``d''. First look
858 * for ~/.mime.types, then look in a system mime.types if we can find one.
859 * The longest match is used so that we can match `ps.gz' when `gz' also
863 int mutt_lookup_mime_type (BODY * att, const char *path)
867 char buf[LONG_STRING];
868 char subtype[STRING], xtype[STRING];
870 int szf, sze, cur_sze;
878 szf = m_strlen(path);
880 for (count = 0; count < 4; count++) {
882 * can't use strtok() because we use it in an inner loop below, so use
883 * a switch statement here instead.
887 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
890 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
893 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
896 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
899 debug_print (1, ("Internal error, count = %d.\n", count));
900 goto bye; /* shouldn't happen */
903 if ((f = fopen (buf, "r")) != NULL) {
904 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
905 /* weed out any comments */
906 if ((p = strchr (buf, '#')))
909 /* remove any leading space. */
910 ct = vskipspaces(buf);
912 /* position on the next field in this line */
913 if ((p = strpbrk (ct, " \t")) == NULL)
918 /* cycle through the file extensions */
919 while ((p = strtok (p, " \t\n"))) {
921 if ((sze > cur_sze) && (szf >= sze) &&
922 (m_strcasecmp(path + szf - sze, p) == 0
923 || ascii_strcasecmp (path + szf - sze, p) == 0)
924 && (szf == sze || path[szf - sze - 1] == '.'))
926 /* get the content-type */
928 if ((p = strchr (ct, '/')) == NULL) {
929 /* malformed line, just skip it. */
934 for (q = p; *q && !ISSPACE (*q); q++);
936 m_strncpy(subtype, sizeof(subtype), p, q - p);
938 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
939 m_strcpy(xtype, sizeof(xtype), ct);
952 if (type != TYPEOTHER || *xtype != '\0') {
954 m_strreplace(&att->subtype, subtype);
955 m_strreplace(&att->xtype, xtype);
961 void mutt_message_to_7bit (BODY * a, FILE * fp)
963 char temp[_POSIX_PATH_MAX];
969 if (!a->filename && fp)
971 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
972 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
977 if (stat (a->filename, &sb) == -1) {
978 mutt_perror ("stat");
981 a->length = sb.st_size;
985 if (!(fpout = safe_fopen (temp, "w+"))) {
986 mutt_perror ("fopen");
990 fseeko (fpin, a->offset, 0);
991 a->parts = mutt_parse_messageRFC822 (fpin, a);
993 transform_to_7bit (a->parts, fpin);
995 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
996 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
998 fputs ("MIME-Version: 1.0\n", fpout);
999 mutt_write_mime_header (a->parts, fpout);
1000 fputc ('\n', fpout);
1001 mutt_write_mime_body (a->parts, fpout);
1013 a->encoding = ENC7BIT;
1014 a->d_filename = a->filename;
1015 if (a->filename && a->unlink)
1016 unlink (a->filename);
1017 a->filename = m_strdup(temp);
1019 if (stat (a->filename, &sb) == -1) {
1020 mutt_perror ("stat");
1023 a->length = sb.st_size;
1024 mutt_free_body (&a->parts);
1025 a->hdr->content = NULL;
1028 static void transform_to_7bit (BODY * a, FILE * fpin)
1030 char buff[_POSIX_PATH_MAX];
1035 for (; a; a = a->next) {
1036 if (a->type == TYPEMULTIPART) {
1037 if (a->encoding != ENC7BIT)
1038 a->encoding = ENC7BIT;
1040 transform_to_7bit (a->parts, fpin);
1042 else if (mutt_is_message_type (a->type, a->subtype)) {
1043 mutt_message_to_7bit (a, fpin);
1047 a->force_charset = 1;
1050 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1051 mutt_perror ("fopen");
1055 mutt_decode_attachment (a, &s);
1057 a->d_filename = a->filename;
1058 a->filename = m_strdup(buff);
1060 if (stat (a->filename, &sb) == -1) {
1061 mutt_perror ("stat");
1064 a->length = sb.st_size;
1066 mutt_update_encoding (a);
1067 if (a->encoding == ENC8BIT)
1068 a->encoding = ENCQUOTEDPRINTABLE;
1069 else if (a->encoding == ENCBINARY)
1070 a->encoding = ENCBASE64;
1075 /* determine which Content-Transfer-Encoding to use */
1076 static void mutt_set_encoding (BODY * b, CONTENT * info)
1078 char send_charset[SHORT_STRING];
1080 if (b->type == TYPETEXT) {
1082 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1083 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1084 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1085 b->encoding = ENCQUOTEDPRINTABLE;
1086 else if (info->hibin)
1087 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1089 b->encoding = ENC7BIT;
1091 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1092 if (info->lobin || info->hibin) {
1093 if (option (OPTALLOW8BIT) && !info->lobin)
1094 b->encoding = ENC8BIT;
1096 mutt_message_to_7bit (b, NULL);
1099 b->encoding = ENC7BIT;
1101 else if (b->type == TYPEAPPLICATION
1102 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1103 b->encoding = ENC7BIT;
1106 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1107 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1110 /* Determine which encoding is smaller */
1111 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1112 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1113 b->encoding = ENCBASE64;
1115 b->encoding = ENCQUOTEDPRINTABLE;
1119 b->encoding = ENC7BIT;
1123 void mutt_stamp_attachment (BODY * a)
1125 a->stamp = time (NULL);
1128 /* Get a body's character set */
1130 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1134 if (b && b->type != TYPETEXT)
1138 p = mutt_get_parameter ("charset", b->parameter);
1141 mutt_canonical_charset (d, dlen, NONULL (p));
1143 m_strcpy(d, dlen, "us-ascii");
1149 /* Assumes called from send mode where BODY->filename points to actual file */
1150 void mutt_update_encoding (BODY * a)
1153 char chsbuff[STRING];
1155 /* override noconv when it's us-ascii */
1156 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1159 if (!a->force_charset && !a->noconv)
1160 mutt_delete_parameter ("charset", &a->parameter);
1162 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1165 mutt_set_encoding (a, info);
1166 mutt_stamp_attachment (a);
1168 p_delete(&a->content);
1173 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1175 char buffer[LONG_STRING];
1178 int cmflags, chflags;
1179 int pgp = hdr->security;
1181 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1182 (hdr->security & ENCRYPT)) {
1183 if (!crypt_valid_passphrase (hdr->security))
1187 mutt_mktemp (buffer);
1188 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1191 body = mutt_new_body ();
1192 body->type = TYPEMESSAGE;
1193 body->subtype = m_strdup("rfc822");
1194 body->filename = m_strdup(buffer);
1197 body->disposition = DISPINLINE;
1200 mutt_parse_mime_message (ctx, hdr);
1205 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1206 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1207 chflags |= CH_MIME | CH_TXTPLAIN;
1208 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1209 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1211 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1212 if (mutt_is_multipart_encrypted (hdr->content)) {
1213 chflags |= CH_MIME | CH_NONEWLINE;
1214 cmflags = M_CM_DECODE_PGP;
1217 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1218 chflags |= CH_MIME | CH_TXTPLAIN;
1219 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1222 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1223 chflags |= CH_MIME | CH_TXTPLAIN;
1224 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1225 pgp &= ~SMIMEENCRYPT;
1229 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1234 body->hdr = header_new();
1235 body->hdr->offset = 0;
1236 /* we don't need the user headers here */
1237 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1238 body->hdr->security = pgp;
1239 mutt_update_encoding (body);
1240 body->parts = body->hdr->content;
1247 BODY *mutt_make_file_attach (const char *path)
1252 att = mutt_new_body ();
1253 att->filename = m_strdup(path);
1255 /* Attempt to determine the appropriate content-type based on the filename
1262 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1263 path)) != TYPEOTHER || *xbuf != '\0') {
1265 att->subtype = m_strdup(buf);
1266 att->xtype = m_strdup(xbuf);
1271 mutt_lookup_mime_type (att, path);
1275 if ((info = mutt_get_content_info (path, att)) == NULL) {
1276 mutt_free_body (&att);
1280 if (!att->subtype) {
1281 if (info->lobin == 0
1282 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1284 * Statistically speaking, there should be more than 10% "lobin"
1285 * chars if this is really a binary file...
1287 att->type = TYPETEXT;
1288 att->subtype = m_strdup("plain");
1291 att->type = TYPEAPPLICATION;
1292 att->subtype = m_strdup("octet-stream");
1296 mutt_update_encoding (att);
1300 static int get_toplevel_encoding (BODY * a)
1304 for (; a; a = a->next) {
1305 if (a->encoding == ENCBINARY)
1307 else if (a->encoding == ENC8BIT)
1314 BODY *mutt_make_multipart (BODY * b)
1318 new = mutt_new_body ();
1319 new->type = TYPEMULTIPART;
1320 new->subtype = m_strdup("mixed");
1321 new->encoding = get_toplevel_encoding (b);
1322 mutt_generate_boundary (&new->parameter);
1324 new->disposition = DISPINLINE;
1330 /* remove the multipart body if it exists */
1331 BODY *mutt_remove_multipart (BODY * b)
1339 mutt_free_body (&t);
1344 char *mutt_make_date (char *s, size_t len)
1346 time_t t = time (NULL);
1347 struct tm *l = localtime (&t);
1348 time_t tz = mutt_local_tz (t);
1352 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1353 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1354 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1355 (int) tz / 60, (int) abs (tz) % 60);
1359 /* wrapper around mutt_write_address() so we can handle very large
1360 recipient lists without needing a huge temporary buffer in memory */
1361 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1365 char buf[LONG_STRING];
1373 rfc822_write_address (buf, sizeof (buf), adr, display);
1374 len = m_strlen(buf);
1375 if (count && linelen + len > 74) {
1377 linelen = len + 8; /* tab is usually about 8 spaces... */
1380 if (count && adr->mailbox) {
1388 if (!adr->group && adr->next && adr->next->mailbox) {
1398 /* arbitrary number of elements to grow the array by */
1403 /* need to write the list in reverse because they are stored in reverse order
1404 * when parsed to speed up threading
1406 void mutt_write_references (LIST * r, FILE * f)
1409 int refcnt = 0, refmax = 0;
1411 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1412 if (refcnt == refmax)
1413 p_realloc(&ref, refmax += REF_INC);
1417 while (refcnt-- > 0) {
1419 fputs (ref[refcnt]->data, f);
1425 /* Note: all RFC2047 encoding should be done outside of this routine, except
1426 * for the "real name." This will allow this routine to be used more than
1427 * once, if necessary.
1429 * Likewise, all IDN processing should happen outside of this routine.
1431 * mode == 1 => "lite" mode (used for edit_hdrs)
1432 * mode == 0 => normal mode. write full header + MIME headers
1433 * mode == -1 => write just the envelope info (used for postponing messages)
1435 * privacy != 0 => will omit any headers which may identify the user.
1436 * Output generated is suitable for being sent through
1437 * anonymous remailer chains.
1441 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1442 int mode, int privacy)
1444 char buffer[LONG_STRING];
1446 LIST *tmp = env->userhdrs;
1447 int has_agent = 0; /* user defined user-agent header field exists */
1448 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1451 if (!option (OPTNEWSSEND))
1453 if (mode == 0 && !privacy)
1454 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1456 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1458 /* OPTUSEFROM is not consulted here so that we can still write a From:
1459 * field if the user sets it with the `my_hdr' command
1461 if (env->from && !privacy) {
1463 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1464 fprintf (fp, "From: %s\n", buffer);
1469 mutt_write_address_list (env->to, fp, 4, 0);
1473 if (!option (OPTNEWSSEND))
1475 if (EDIT_HEADER("To:"))
1476 fputs ("To:\n", fp);
1480 mutt_write_address_list (env->cc, fp, 4, 0);
1484 if (!option (OPTNEWSSEND))
1486 if (EDIT_HEADER("Cc:"))
1487 fputs ("Cc:\n", fp);
1490 if (mode != 0 || option (OPTWRITEBCC)) {
1491 fputs ("Bcc: ", fp);
1492 mutt_write_address_list (env->bcc, fp, 5, 0);
1497 if (!option (OPTNEWSSEND))
1499 if (EDIT_HEADER("Bcc:"))
1500 fputs ("Bcc:\n", fp);
1503 if (env->newsgroups)
1504 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1505 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1506 fputs ("Newsgroups:\n", fp);
1508 if (env->followup_to)
1509 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1510 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1511 fputs ("Followup-To:\n", fp);
1513 if (env->x_comment_to)
1514 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1515 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1516 EDIT_HEADER("X-Comment-To:"))
1517 fputs ("X-Comment-To:\n", fp);
1521 fprintf (fp, "Subject: %s\n", env->subject);
1522 else if (mode == 1 && EDIT_HEADER("Subject:"))
1523 fputs ("Subject:\n", fp);
1525 /* save message id if the user has set it */
1526 if (env->message_id && !privacy)
1527 fprintf (fp, "Message-ID: %s\n", env->message_id);
1529 if (env->reply_to) {
1530 fputs ("Reply-To: ", fp);
1531 mutt_write_address_list (env->reply_to, fp, 10, 0);
1533 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1534 fputs ("Reply-To:\n", fp);
1536 if (env->mail_followup_to)
1538 if (!option (OPTNEWSSEND))
1541 fputs ("Mail-Followup-To: ", fp);
1542 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1546 if (env->references) {
1547 fputs ("References:", fp);
1548 mutt_write_references (env->references, fp);
1552 /* Add the MIME headers */
1553 fputs ("MIME-Version: 1.0\n", fp);
1554 mutt_write_mime_header (attach, fp);
1557 if (env->in_reply_to) {
1558 fputs ("In-Reply-To:", fp);
1559 mutt_write_references (env->in_reply_to, fp);
1565 /* Add any user defined headers */
1566 for (; tmp; tmp = tmp->next) {
1567 if ((p = strchr (tmp->data, ':'))) {
1568 p = vskipspaces(p + 1);
1570 continue; /* don't emit empty fields. */
1572 /* check to see if the user has overridden the user-agent field */
1573 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1579 fputs (tmp->data, fp);
1584 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1587 if (OperatingSystem != NULL) {
1588 os = OperatingSystem;
1591 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1593 /* Add a vanity header */
1594 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1597 list_del (&hdrs, (list_del_t*)xmemfree);
1599 return (ferror (fp) == 0 ? 0 : -1);
1602 static void encode_headers (LIST * h)
1608 for (; h; h = h->next) {
1609 if (!(p = strchr (h->data, ':')))
1613 p = vskipspaces(p + 1);
1619 rfc2047_encode_string (&tmp);
1620 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1622 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1628 const char *mutt_fqdn (short may_hide_host)
1632 if (Fqdn && Fqdn[0] != '@') {
1635 if (may_hide_host && option (OPTHIDDENHOST)) {
1636 if ((p = strchr (Fqdn, '.')))
1639 /* sanity check: don't hide the host if
1640 * the fqdn is something like detebe.org.
1643 if (!p || !(q = strchr (p, '.')))
1651 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1652 static char mutt_normalized_char(char c)
1654 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1657 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1659 #define APPEND_FMT(fmt, arg) \
1661 int snlen = snprintf(buf, len, fmt, arg); \
1666 #define APPEND_BYTE(c) \
1683 APPEND_BYTE(mutt_normalized_char(c));
1691 APPEND_FMT("%02d", tm->tm_mday);
1694 APPEND_FMT("%02d", tm->tm_hour);
1697 APPEND_FMT("%02d", tm->tm_mon + 1);
1700 APPEND_FMT("%02d", tm->tm_min);
1703 APPEND_FMT("%lo", (unsigned long)now);
1706 APPEND_FMT("%u", (unsigned int)getpid());
1709 APPEND_FMT("%c", MsgIdPfx);
1710 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1713 APPEND_FMT("%u", (unsigned int)rand());
1716 APPEND_FMT("%x", (unsigned int)rand());
1719 APPEND_FMT("%02d", tm->tm_sec);
1722 APPEND_FMT("%u", (unsigned int) now);
1725 APPEND_FMT("%x", (unsigned int) now);
1727 case 'Y': /* this will break in the year 10000 ;-) */
1728 APPEND_FMT("%04d", tm->tm_year + 1900);
1733 default: /* invalid formats are replaced by '.' */
1735 m_strncat(buf, len, ".", 1);
1745 char *mutt_gen_msgid (void)
1747 char buf[SHORT_STRING];
1748 char localpart[SHORT_STRING];
1749 unsigned int localpart_length;
1752 if (!(fqdn = mutt_fqdn (0)))
1753 fqdn = NONULL (Hostname);
1755 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1757 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1759 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1760 return (m_strdup(buf));
1763 static RETSIGTYPE alarm_handler (int sig)
1768 /* invoke sendmail in a subshell
1769 path (in) path to program to execute
1770 args (in) arguments to pass to program
1771 msg (in) temp file containing message to send
1772 tempfile (out) if sendmail is put in the background, this points
1773 to the temporary file containing the stdout of the
1776 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1782 mutt_block_signals_system ();
1785 /* we also don't want to be stopped right now */
1786 sigaddset (&set, SIGTSTP);
1787 sigprocmask (SIG_BLOCK, &set, NULL);
1789 if (SendmailWait >= 0) {
1790 char tmp[_POSIX_PATH_MAX];
1793 *tempfile = m_strdup(tmp);
1796 if ((pid = fork ()) == 0) {
1797 struct sigaction act, oldalrm;
1799 /* save parent's ID before setsid() */
1802 /* we want the delivery to continue even after the main process dies,
1803 * so we put ourselves into another session right away
1807 /* next we close all open files */
1808 #if defined(OPEN_MAX)
1809 for (fd = 0; fd < OPEN_MAX; fd++)
1811 #elif defined(_POSIX_OPEN_MAX)
1812 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1820 /* now the second fork() */
1821 if ((pid = fork ()) == 0) {
1822 /* "msg" will be opened as stdin */
1823 if (open (msg, O_RDONLY, 0) < 0) {
1829 if (SendmailWait >= 0) {
1830 /* *tempfile will be opened as stdout */
1831 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1834 /* redirect stderr to *tempfile too */
1839 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1841 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1848 else if (pid == -1) {
1854 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1855 * SendmailWait = 0: wait forever
1856 * SendmailWait < 0: don't wait
1858 if (SendmailWait > 0) {
1860 act.sa_handler = alarm_handler;
1862 /* need to make sure waitpid() is interrupted on SIGALRM */
1863 act.sa_flags = SA_INTERRUPT;
1867 sigemptyset (&act.sa_mask);
1868 sigaction (SIGALRM, &act, &oldalrm);
1869 alarm (SendmailWait);
1871 else if (SendmailWait < 0)
1872 _exit (0xff & EX_OK);
1874 if (waitpid (pid, &st, 0) > 0) {
1875 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1876 if (SendmailWait && st == (0xff & EX_OK)) {
1877 unlink (*tempfile); /* no longer needed */
1882 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1883 if (SendmailWait > 0) {
1889 /* reset alarm; not really needed, but... */
1891 sigaction (SIGALRM, &oldalrm, NULL);
1893 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1894 /* the parent is already dead */
1902 sigprocmask (SIG_UNBLOCK, &set, NULL);
1904 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1905 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1907 st = S_ERR; /* error */
1909 mutt_unblock_signals_system (1);
1914 static const char **
1915 add_args(const char **args, size_t *argslen, size_t *argsmax, address_t * addr)
1917 for (; addr; addr = addr->next) {
1918 /* weed out group mailboxes, since those are for display only */
1919 if (addr->mailbox && !addr->group) {
1920 if (*argslen == *argsmax)
1921 p_realloc(&args, *argsmax += 5);
1922 args[(*argslen)++] = addr->mailbox;
1928 static const char **
1929 add_option(const char **args, size_t *argslen, size_t *argsmax, const char *s)
1931 if (*argslen == *argsmax) {
1932 p_realloc(&args, *argsmax += 5);
1934 args[(*argslen)++] = s;
1938 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1939 address_t * to, address_t * cc, address_t * bcc, /* recips */
1940 const char *msg, /* file containing message */
1942 { /* message contains 8bit chars */
1943 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1944 const char **args = NULL;
1945 size_t argslen = 0, argsmax = 0;
1949 if (option (OPTNEWSSEND)) {
1950 char cmd[LONG_STRING];
1952 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1955 i = nntp_post (msg);
1964 s = m_strdup(Sendmail);
1968 while ((ps = strtok (ps, " "))) {
1969 if (argslen == argsmax)
1970 p_realloc(&args, argsmax += 5);
1973 args[argslen++] = ps;
1975 path = m_strdup(ps);
1976 ps = strrchr (ps, '/');
1981 args[argslen++] = ps;
1988 if (!option (OPTNEWSSEND)) {
1990 if (eightbit && option (OPTUSE8BITMIME))
1991 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1993 if (option (OPTENVFROM)) {
1994 address_t *f = NULL;
1997 else if (from && !from->next)
2000 args = add_option (args, &argslen, &argsmax, "-f");
2001 args = add_args (args, &argslen, &argsmax, f);
2005 args = add_option (args, &argslen, &argsmax, "-N");
2006 args = add_option (args, &argslen, &argsmax, DsnNotify);
2009 args = add_option (args, &argslen, &argsmax, "-R");
2010 args = add_option (args, &argslen, &argsmax, DsnReturn);
2012 args = add_option (args, &argslen, &argsmax, "--");
2013 args = add_args (args, &argslen, &argsmax, to);
2014 args = add_args (args, &argslen, &argsmax, cc);
2015 args = add_args (args, &argslen, &argsmax, bcc);
2020 if (argslen == argsmax)
2021 p_realloc(&args, ++argsmax);
2023 args[argslen++] = NULL;
2025 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2027 const char *e = mutt_strsysexit (i);
2029 e = mutt_strsysexit (i);
2030 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2035 if (stat (childout, &st) == 0 && st.st_size > 0)
2036 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2044 p_delete(&childout);
2049 if (i == (EX_OK & 0xff))
2051 else if (i == S_BKG)
2058 int mutt_invoke_mta (address_t * from, /* the sender */
2059 address_t * to, address_t * cc, address_t * bcc, /* recips */
2060 const char *msg, /* file containing message */
2062 { /* message contains 8bit chars */
2065 if (!option (OPTNEWSSEND))
2068 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2071 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2074 /* appends string 'b' to string 'a', and returns the pointer to the new
2076 char *mutt_append_string (char *a, const char *b)
2078 size_t la = m_strlen(a);
2080 p_realloc(&a, la + m_strlen(b) + 1);
2081 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2085 /* returns 1 if char `c' needs to be quoted to protect from shell
2086 interpretation when executing commands in a subshell */
2087 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2089 /* returns 1 if string `s' contains characters which could cause problems
2090 when used on a command line to execute a command */
2091 int mutt_needs_quote (const char *s)
2094 if (INVALID_CHAR (*s))
2101 /* Quote a string to prevent shell escapes when this string is used on the
2102 command line to send mail. */
2103 char *mutt_quote_string (const char *s)
2108 rlen = m_strlen(s) + 3;
2109 pr = r = p_new(char, rlen);
2112 if (INVALID_CHAR (*s)) {
2115 p_realloc(&r, ++rlen);
2126 /* For postponing (!final) do the necessary encodings only */
2127 void mutt_prepare_envelope (ENVELOPE * env, int final)
2129 char buffer[LONG_STRING];
2132 if (env->bcc && !(env->to || env->cc)) {
2133 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2134 * recipients if there is no To: or Cc: field, so attempt to suppress
2135 * it by using an empty To: field.
2137 env->to = address_new ();
2139 env->to->next = address_new ();
2142 rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
2145 env->to->mailbox = m_strdup(buffer);
2148 mutt_set_followup_to (env);
2150 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2151 env->message_id = mutt_gen_msgid ();
2154 /* Take care of 8-bit => 7-bit conversion. */
2155 rfc2047_encode_adrlist (env->to, "To");
2156 rfc2047_encode_adrlist (env->cc, "Cc");
2157 rfc2047_encode_adrlist (env->bcc, "Bcc");
2158 rfc2047_encode_adrlist (env->from, "From");
2159 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2160 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2164 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2167 rfc2047_encode_string (&env->subject);
2169 encode_headers (env->userhdrs);
2172 void mutt_unprepare_envelope (ENVELOPE * env)
2176 for (item = env->userhdrs; item; item = item->next)
2177 rfc2047_decode (&item->data);
2179 address_delete (&env->mail_followup_to);
2181 /* back conversions */
2182 rfc2047_decode_adrlist (env->to);
2183 rfc2047_decode_adrlist (env->cc);
2184 rfc2047_decode_adrlist (env->bcc);
2185 rfc2047_decode_adrlist (env->from);
2186 rfc2047_decode_adrlist (env->reply_to);
2187 rfc2047_decode (&env->subject);
2190 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2191 const char *resent_from, address_t * env_from)
2195 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2196 MESSAGE *msg = NULL;
2199 /* Try to bounce each message out, aborting if we get any failures. */
2200 for (i = 0; i < Context->msgcount; i++)
2201 if (Context->hdrs[i]->tagged)
2203 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2208 /* If we failed to open a message, return with error */
2209 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2215 mutt_mktemp (tempfile);
2216 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2217 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2219 if (!option (OPTBOUNCEDELIVERED))
2220 ch_flags |= CH_WEED_DELIVERED;
2222 fseeko (fp, h->offset, 0);
2223 fprintf (f, "Resent-From: %s", resent_from);
2224 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2225 if (MsgIdFormat && *MsgIdFormat)
2226 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2227 fputs ("Resent-To: ", f);
2228 mutt_write_address_list (to, f, 11, 0);
2229 mutt_copy_header (fp, h, f, ch_flags, NULL);
2231 mutt_copy_bytes (fp, f, h->content->length);
2234 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2235 h->content->encoding == ENC8BIT);
2239 mx_close_message (&msg);
2244 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2247 const char *fqdn = mutt_fqdn (1);
2248 char resent_from[STRING];
2252 resent_from[0] = '\0';
2253 from = mutt_default_from ();
2256 rfc822_qualify (from, fqdn);
2258 rfc2047_encode_adrlist (from, "Resent-From");
2259 if (mutt_addrlist_to_idna (from, &err)) {
2260 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2263 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2266 unset_option (OPTNEWSSEND);
2269 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2271 address_delete (&from);
2277 /* given a list of addresses, return a list of unique addresses */
2278 address_t *mutt_remove_duplicates (address_t * addr)
2280 address_t *top = addr;
2281 address_t **last = ⊤
2286 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2287 if (tmp->mailbox && addr->mailbox &&
2288 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2295 debug_print (2, ("Removing %s\n", addr->mailbox));
2300 address_delete (&addr);
2313 static void set_noconv_flags (BODY * b, short flag)
2315 for (; b; b = b->next) {
2316 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2317 set_noconv_flags (b->parts, flag);
2318 else if (b->type == TYPETEXT && b->noconv) {
2320 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2322 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2327 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2328 int post, char *fcc)
2332 char tempfile[_POSIX_PATH_MAX];
2333 FILE *tempfp = NULL;
2337 set_noconv_flags (hdr->content, 1);
2339 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2340 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2344 /* We need to add a Content-Length field to avoid problems where a line in
2345 * the message body begins with "From "
2347 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2348 mutt_mktemp (tempfile);
2349 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2350 mutt_perror (tempfile);
2351 mx_close_mailbox (&f, NULL);
2356 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2357 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2358 mx_close_mailbox (&f, NULL);
2362 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2363 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2365 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2368 /* (postponment) if this was a reply of some sort, <msgid> contians the
2369 * Message-ID: of message replied to. Save it using a special X-Mutt-
2370 * header so it can be picked up if the message is recalled at a later
2371 * point in time. This will allow the message to be marked as replied if
2372 * the same mailbox is still open.
2375 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2377 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2378 * it can be picked up when the message is recalled
2381 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2382 fprintf (msg->fp, "Status: RO\n");
2386 /* (postponment) if the mail is to be signed or encrypted, save this info */
2387 if (post && (hdr->security & APPLICATION_PGP)) {
2388 fputs ("X-Mutt-PGP: ", msg->fp);
2389 if (hdr->security & ENCRYPT)
2390 fputc ('E', msg->fp);
2391 if (hdr->security & SIGN) {
2392 fputc ('S', msg->fp);
2393 if (PgpSignAs && *PgpSignAs)
2394 fprintf (msg->fp, "<%s>", PgpSignAs);
2396 if (hdr->security & INLINE)
2397 fputc ('I', msg->fp);
2398 fputc ('\n', msg->fp);
2401 /* (postponment) if the mail is to be signed or encrypted, save this info */
2402 if (post && (hdr->security & APPLICATION_SMIME)) {
2403 fputs ("X-Mutt-SMIME: ", msg->fp);
2404 if (hdr->security & ENCRYPT) {
2405 fputc ('E', msg->fp);
2406 if (SmimeCryptAlg && *SmimeCryptAlg)
2407 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2409 if (hdr->security & SIGN) {
2410 fputc ('S', msg->fp);
2411 if (SmimeDefaultKey && *SmimeDefaultKey)
2412 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2414 if (hdr->security & INLINE)
2415 fputc ('I', msg->fp);
2416 fputc ('\n', msg->fp);
2420 /* (postponement) if the mail is to be sent through a mixmaster
2421 * chain, save that information
2424 if (post && hdr->chain && hdr->chain) {
2427 fputs ("X-Mutt-Mix:", msg->fp);
2428 for (p = hdr->chain; p; p = p->next)
2429 fprintf (msg->fp, " %s", (char *) p->data);
2431 fputc ('\n', msg->fp);
2436 char sasha[LONG_STRING];
2439 mutt_write_mime_body (hdr->content, tempfp);
2441 /* make sure the last line ends with a newline. Emacs doesn't ensure
2442 * this will happen, and it can cause problems parsing the mailbox
2445 fseeko (tempfp, -1, 2);
2446 if (fgetc (tempfp) != '\n') {
2447 fseeko (tempfp, 0, 2);
2448 fputc ('\n', tempfp);
2452 if (ferror (tempfp)) {
2453 debug_print (1, ("%s: write failed.\n", tempfile));
2456 mx_commit_message (msg, &f); /* XXX - really? */
2457 mx_close_message (&msg);
2458 mx_close_mailbox (&f, NULL);
2462 /* count the number of lines */
2464 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2466 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2467 fprintf (msg->fp, "Lines: %d\n\n", lines);
2469 /* copy the body and clean up */
2471 r = mutt_copy_stream (tempfp, msg->fp);
2472 if (fclose (tempfp) != 0)
2474 /* if there was an error, leave the temp version */
2479 fputc ('\n', msg->fp); /* finish off the header */
2480 r = mutt_write_mime_body (hdr->content, msg->fp);
2483 if (mx_commit_message (msg, &f) != 0)
2485 mx_close_message (&msg);
2486 mx_close_mailbox (&f, NULL);
2489 set_noconv_flags (hdr->content, 0);