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.
19 #include "recvattach.h"
20 #include "mutt_curses.h"
28 #include "mutt_crypt.h"
29 #include "mutt_idna.h"
35 #include "lib/debug.h"
46 #include <sys/utsname.h>
49 # include "mutt_libesmtp.h"
50 #endif /* USE_LIBESMTP */
56 #ifdef HAVE_SYSEXITS_H
58 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
62 /* If you are debugging this file, comment out the following line. */
71 extern char RFC822Specials[];
73 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
75 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
78 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
79 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
80 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
81 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
85 static char MsgIdPfx = 'A';
87 static void transform_to_7bit (BODY * a, FILE * fpin);
89 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
92 char line[77], savechar;
94 while ((c = fgetconv (fc)) != EOF) {
95 /* Wrap the line if needed. */
96 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
97 /* If the last character is "quoted", then be sure to move all three
98 * characters to the next line. Otherwise, just move the last
101 if (line[linelen - 3] == '=') {
102 line[linelen - 3] = 0;
107 line[1] = line[linelen - 2];
108 line[2] = line[linelen - 1];
112 savechar = line[linelen - 1];
113 line[linelen - 1] = '=';
122 /* Escape lines that begin with/only contain "the message separator". */
123 if (linelen == 4 && !str_ncmp ("From", line, 4)) {
124 strfcpy (line, "=46rom", sizeof (line));
127 else if (linelen == 4 && !str_ncmp ("from", line, 4)) {
128 strfcpy (line, "=66rom", sizeof (line));
131 else if (linelen == 1 && line[0] == '.') {
132 strfcpy (line, "=2E", sizeof (line));
137 if (c == '\n' && istext) {
138 /* Check to make sure there is no trailing space on this line. */
140 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
142 sprintf (line + linelen - 1, "=%2.2X",
143 (unsigned char) line[linelen - 1]);
147 int savechar = line[linelen - 1];
149 line[linelen - 1] = '=';
152 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
162 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
163 /* Check to make sure there is enough room for the quoted character.
164 * If not, wrap to the next line.
167 line[linelen++] = '=';
173 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
177 /* Don't worry about wrapping the line here. That will happen during
178 * the next iteration when I'll also know what the next character is.
184 /* Take care of anything left in the buffer */
186 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
187 /* take care of trailing whitespace */
189 sprintf (line + linelen - 1, "=%2.2X",
190 (unsigned char) line[linelen - 1]);
192 savechar = line[linelen - 1];
193 line[linelen - 1] = '=';
197 sprintf (line, "=%2.2X", (unsigned char) savechar);
206 static char b64_buffer[3];
207 static short b64_num;
208 static short b64_linelen;
210 static void b64_flush (FILE * fout)
217 if (b64_linelen >= 72) {
222 for (i = b64_num; i < 3; i++)
223 b64_buffer[i] = '\0';
225 fputc (B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
228 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
233 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
237 fputc (B64Chars[b64_buffer[2] & 0x3f], fout);
242 while (b64_linelen % 4) {
251 static void b64_putc (char c, FILE * fout)
256 b64_buffer[b64_num++] = c;
260 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
264 b64_num = b64_linelen = 0;
266 while ((ch = fgetconv (fc)) != EOF) {
267 if (istext && ch == '\n' && ch1 != '\r')
268 b64_putc ('\r', fout);
276 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
280 while ((ch = fgetconv (fc)) != EOF)
285 int mutt_write_mime_header (BODY * a, FILE * f)
295 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
298 len = 25 + str_len (a->subtype); /* approximate len. of content-type */
300 for (p = a->parameter; p; p = p->next) {
309 tmp = str_dup (p->value);
310 encode = rfc2231_encode_string (&tmp);
311 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
313 /* Dirty hack to make messages readable by Outlook Express
314 * for the Mac: force quotes around the boundary parameter
315 * even when they aren't needed.
318 if (!ascii_strcasecmp (p->attribute, "boundary")
319 && !strcmp (buffer, tmp))
320 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
324 tmplen = str_len (buffer) + str_len (p->attribute) + 1;
326 if (len + tmplen + 2 > 76) {
335 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
343 fprintf (f, "Content-Description: %s\n", a->description);
345 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
348 if (!(fn = a->d_filename))
354 /* Strip off the leading path... */
355 if ((t = strrchr (fn, '/')))
362 encode = rfc2231_encode_string (&tmp);
363 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
365 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
371 if (a->encoding != ENC7BIT)
372 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
374 /* Do NOT add the terminator here!!! */
375 return (ferror (f) ? -1 : 0);
378 # define write_as_text_part(a) (mutt_is_text_part(a) \
379 || ((WithCrypto & APPLICATION_PGP)\
380 && mutt_is_application_pgp(a)))
382 int mutt_write_mime_body (BODY * a, FILE * f)
384 char *p, boundary[SHORT_STRING];
385 char send_charset[SHORT_STRING];
390 if (a->type == TYPEMULTIPART) {
391 /* First, find the boundary to use */
392 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
393 debug_print (1, ("no boundary parameter found!\n"));
394 mutt_error _("No boundary parameter found! [report this error]");
398 strfcpy (boundary, p, sizeof (boundary));
400 for (t = a->parts; t; t = t->next) {
401 fprintf (f, "\n--%s\n", boundary);
402 if (mutt_write_mime_header (t, f) == -1)
405 if (mutt_write_mime_body (t, f) == -1)
408 fprintf (f, "\n--%s--\n", boundary);
409 return (ferror (f) ? -1 : 0);
412 /* This is pretty gross, but it's the best solution for now... */
413 if ((WithCrypto & APPLICATION_PGP)
414 && a->type == TYPEAPPLICATION
415 && str_cmp (a->subtype, "pgp-encrypted") == 0) {
416 fputs ("Version: 1\n", f);
420 if ((fpin = fopen (a->filename, "r")) == NULL) {
421 debug_print (1, ("%s no longer exists!\n", a->filename));
422 mutt_error (_("%s no longer exists!"), a->filename);
426 if (a->type == TYPETEXT && (!a->noconv))
427 fc = fgetconv_open (fpin, a->file_charset,
428 mutt_get_body_charset (send_charset,
429 sizeof (send_charset), a), 0);
431 fc = fgetconv_open (fpin, 0, 0, 0);
433 if (a->encoding == ENCQUOTEDPRINTABLE)
434 encode_quoted (fc, f, write_as_text_part (a));
435 else if (a->encoding == ENCBASE64)
436 encode_base64 (fc, f, write_as_text_part (a));
437 else if (a->type == TYPETEXT && (!a->noconv))
438 encode_8bit (fc, f, write_as_text_part (a));
440 mutt_copy_stream (fpin, f);
442 fgetconv_close (&fc);
445 return (ferror (f) ? -1 : 0);
448 #undef write_as_text_part
450 #define BOUNDARYLEN 16
451 void mutt_generate_boundary (PARAMETER ** parm)
453 char rs[BOUNDARYLEN + 1];
458 for (i = 0; i < BOUNDARYLEN; i++)
459 *p++ = B64Chars[LRAND () % sizeof (B64Chars)];
462 mutt_set_parameter ("boundary", rs, parm);
474 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
478 int whitespace = s->whitespace;
480 int linelen = s->linelen;
481 int was_cr = s->was_cr;
483 if (!d) { /* This signals EOF */
486 if (linelen > info->linemax)
487 info->linemax = linelen;
492 for (; dlen; d++, dlen--) {
505 if (linelen > info->linemax)
506 info->linemax = linelen;
521 if (linelen > info->linemax)
522 info->linemax = linelen;
527 else if (ch == '\r') {
535 else if (ch == '\t' || ch == '\f') {
539 else if (ch < 32 || ch == 127)
543 if ((ch == 'F') || (ch == 'f'))
553 if (linelen == 2 && ch != 'r')
555 else if (linelen == 3 && ch != 'o')
557 else if (linelen == 4) {
570 if (ch != ' ' && ch != '\t')
575 s->whitespace = whitespace;
577 s->linelen = linelen;
582 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
583 #define BUGGY_ICONV 1
586 * Find the best charset conversion of the file from fromcode into one
587 * of the tocodes. If successful, set *tocode and CONTENT *info and
588 * return the number of characters converted inexactly. If no
589 * conversion was possible, return -1.
591 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
592 * which would otherwise prevent us from knowing the number of inexact
593 * conversions. Where the candidate target charset is UTF-8 we avoid
594 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
595 * fails with some libraries.
597 * We assume that the output from iconv is never more than 4 times as
598 * long as the input for any pair of charsets we might be interested
601 static size_t convert_file_to (FILE * file, const char *fromcode,
602 int ncodes, const char **tocodes,
603 int *tocode, CONTENT * info)
607 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
608 ICONV_CONST char *ib, *ub;
610 size_t ibl, obl, ubl, ubl1, n, ret;
613 CONTENT_STATE *states;
616 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
617 if (cd1 == (iconv_t) (-1))
620 cd = mem_calloc (ncodes, sizeof (iconv_t));
621 score = mem_calloc (ncodes, sizeof (size_t));
622 states = mem_calloc (ncodes, sizeof (CONTENT_STATE));
623 infos = mem_calloc (ncodes, sizeof (CONTENT));
625 for (i = 0; i < ncodes; i++)
626 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
627 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
629 /* Special case for conversion to UTF-8 */
630 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
636 /* Try to fill input buffer */
637 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
640 /* Convert to UTF-8 */
642 ob = bufu, obl = sizeof (bufu);
643 n = iconv (cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
644 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
645 if (n == (size_t) (-1) &&
646 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
647 assert (errno == EILSEQ ||
648 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
654 /* Convert from UTF-8 */
655 for (i = 0; i < ncodes; i++)
656 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
657 ub = bufu, ubl = ubl1;
658 ob = bufo, obl = sizeof (bufo);
659 n = iconv (cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
660 if (n == (size_t) (-1)) {
661 assert (errno == E2BIG ||
662 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
663 score[i] = (size_t) (-1);
667 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
670 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
671 /* Special case for conversion to UTF-8 */
672 update_content_info (&infos[i], &states[i], bufu, ubl1);
675 /* Save unused input */
676 memmove (bufi, ib, ibl);
677 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
684 /* Find best score */
686 for (i = 0; i < ncodes; i++) {
687 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
688 /* Special case for conversion to UTF-8 */
693 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
695 else if (ret == (size_t) (-1) || score[i] < ret) {
702 if (ret != (size_t) (-1)) {
703 memcpy (info, &infos[*tocode], sizeof (CONTENT));
704 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
708 for (i = 0; i < ncodes; i++)
709 if (cd[i] != (iconv_t) (-1))
721 #endif /* !HAVE_ICONV */
725 * Find the first of the fromcodes that gives a valid conversion and
726 * the best charset conversion of the file into one of the tocodes. If
727 * successful, set *fromcode and *tocode to dynamically allocated
728 * strings, set CONTENT *info, and return the number of characters
729 * converted inexactly. If no conversion was possible, return -1.
731 * Both fromcodes and tocodes may be colon-separated lists of charsets.
732 * However, if fromcode is zero then fromcodes is assumed to be the
733 * name of a single charset even if it contains a colon.
735 static size_t convert_file_from_to (FILE * file,
736 const char *fromcodes,
737 const char *tocodes, char **fromcode,
738 char **tocode, CONTENT * info)
746 /* Count the tocodes */
748 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
749 if ((c1 = strchr (c, ':')) == c)
755 tcode = mem_malloc (ncodes * sizeof (char *));
756 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
757 if ((c1 = strchr (c, ':')) == c)
759 tcode[i] = str_substrdup (c, c1);
764 /* Try each fromcode in turn */
765 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
766 if ((c1 = strchr (c, ':')) == c)
768 fcode = str_substrdup (c, c1);
770 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
772 if (ret != (size_t) (-1)) {
782 /* There is only one fromcode */
783 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
785 if (ret != (size_t) (-1)) {
792 for (i = 0; i < ncodes; i++)
793 mem_free (&tcode[i]);
801 * Analyze the contents of a file to determine which MIME encoding to use.
802 * Also set the body charset, sometimes, or not.
804 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
809 char *fromcode = NULL;
820 if (stat (fname, &sb) == -1) {
821 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
825 if (!S_ISREG (sb.st_mode)) {
826 mutt_error (_("%s isn't a regular file."), fname);
830 if ((fp = fopen (fname, "r")) == NULL) {
831 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
835 info = mem_calloc (1, sizeof (CONTENT));
836 memset (&state, 0, sizeof (state));
838 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
839 char *chs = mutt_get_parameter ("charset", b->parameter);
840 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
841 FileCharset : Charset) : Charset;
842 if (Charset && (chs || SendCharset) &&
843 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
844 &fromcode, &tocode, info) != (size_t) (-1)) {
846 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
847 mutt_set_parameter ("charset", chsbuf, &b->parameter);
849 b->file_charset = fromcode;
857 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
858 update_content_info (info, &state, buffer, r);
859 update_content_info (info, &state, 0, 0);
863 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
864 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
866 && !mutt_is_us_ascii (Charset) ? Charset :
867 "unknown-8bit"), &b->parameter);
872 /* Given a file with path ``s'', see if there is a registered MIME type.
873 * returns the major MIME type, and copies the subtype to ``d''. First look
874 * for ~/.mime.types, then look in a system mime.types if we can find one.
875 * The longest match is used so that we can match `ps.gz' when `gz' also
879 int mutt_lookup_mime_type (BODY * att, const char *path)
883 char buf[LONG_STRING];
884 char subtype[STRING], xtype[STRING];
886 int szf, sze, cur_sze;
894 szf = str_len (path);
896 for (count = 0; count < 3; count++) {
898 * can't use strtok() because we use it in an inner loop below, so use
899 * a switch statement here instead.
903 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
906 strfcpy (buf, SYSCONFDIR "/muttng-mime.types", sizeof (buf));
909 strfcpy (buf, PKGDATADIR "/mime.types", sizeof (buf));
912 debug_print (1, ("Internal error, count = %d.\n", count));
913 goto bye; /* shouldn't happen */
916 if ((f = fopen (buf, "r")) != NULL) {
917 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
918 /* weed out any comments */
919 if ((p = strchr (buf, '#')))
922 /* remove any leading space. */
926 /* position on the next field in this line */
927 if ((p = strpbrk (ct, " \t")) == NULL)
932 /* cycle through the file extensions */
933 while ((p = strtok (p, " \t\n"))) {
935 if ((sze > cur_sze) && (szf >= sze) &&
936 (str_casecmp (path + szf - sze, p) == 0
937 || ascii_strcasecmp (path + szf - sze, p) == 0) && (szf == sze
944 /* get the content-type */
946 if ((p = strchr (ct, '/')) == NULL) {
947 /* malformed line, just skip it. */
952 for (q = p; *q && !ISSPACE (*q); q++);
954 str_substrcpy (subtype, p, q, sizeof (subtype));
956 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
957 strfcpy (xtype, ct, sizeof (xtype));
970 if (type != TYPEOTHER || *xtype != '\0') {
972 str_replace (&att->subtype, subtype);
973 str_replace (&att->xtype, xtype);
979 void mutt_message_to_7bit (BODY * a, FILE * fp)
981 char temp[_POSIX_PATH_MAX];
987 if (!a->filename && fp)
989 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
990 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
995 if (stat (a->filename, &sb) == -1) {
996 mutt_perror ("stat");
999 a->length = sb.st_size;
1003 if (!(fpout = safe_fopen (temp, "w+"))) {
1004 mutt_perror ("fopen");
1008 fseek (fpin, a->offset, 0);
1009 a->parts = mutt_parse_messageRFC822 (fpin, a);
1011 transform_to_7bit (a->parts, fpin);
1013 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1014 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1016 fputs ("Mime-Version: 1.0\n", fpout);
1017 mutt_write_mime_header (a->parts, fpout);
1018 fputc ('\n', fpout);
1019 mutt_write_mime_body (a->parts, fpout);
1031 a->encoding = ENC7BIT;
1032 a->d_filename = a->filename;
1033 if (a->filename && a->unlink)
1034 unlink (a->filename);
1035 a->filename = str_dup (temp);
1037 if (stat (a->filename, &sb) == -1) {
1038 mutt_perror ("stat");
1041 a->length = sb.st_size;
1042 mutt_free_body (&a->parts);
1043 a->hdr->content = NULL;
1046 static void transform_to_7bit (BODY * a, FILE * fpin)
1048 char buff[_POSIX_PATH_MAX];
1052 memset (&s, 0, sizeof (s));
1053 for (; a; a = a->next) {
1054 if (a->type == TYPEMULTIPART) {
1055 if (a->encoding != ENC7BIT)
1056 a->encoding = ENC7BIT;
1058 transform_to_7bit (a->parts, fpin);
1060 else if (mutt_is_message_type (a->type, a->subtype)) {
1061 mutt_message_to_7bit (a, fpin);
1065 a->force_charset = 1;
1068 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1069 mutt_perror ("fopen");
1073 mutt_decode_attachment (a, &s);
1075 a->d_filename = a->filename;
1076 a->filename = str_dup (buff);
1078 if (stat (a->filename, &sb) == -1) {
1079 mutt_perror ("stat");
1082 a->length = sb.st_size;
1084 mutt_update_encoding (a);
1085 if (a->encoding == ENC8BIT)
1086 a->encoding = ENCQUOTEDPRINTABLE;
1087 else if (a->encoding == ENCBINARY)
1088 a->encoding = ENCBASE64;
1093 /* determine which Content-Transfer-Encoding to use */
1094 static void mutt_set_encoding (BODY * b, CONTENT * info)
1096 char send_charset[SHORT_STRING];
1098 if (b->type == TYPETEXT) {
1100 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1101 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1102 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1103 b->encoding = ENCQUOTEDPRINTABLE;
1104 else if (info->hibin)
1105 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1107 b->encoding = ENC7BIT;
1109 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1110 if (info->lobin || info->hibin) {
1111 if (option (OPTALLOW8BIT) && !info->lobin)
1112 b->encoding = ENC8BIT;
1114 mutt_message_to_7bit (b, NULL);
1117 b->encoding = ENC7BIT;
1119 else if (b->type == TYPEAPPLICATION
1120 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1121 b->encoding = ENC7BIT;
1124 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1125 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1128 /* Determine which encoding is smaller */
1129 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1130 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1131 b->encoding = ENCBASE64;
1133 b->encoding = ENCQUOTEDPRINTABLE;
1137 b->encoding = ENC7BIT;
1141 void mutt_stamp_attachment (BODY * a)
1143 a->stamp = time (NULL);
1146 /* Get a body's character set */
1148 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1152 if (b && b->type != TYPETEXT)
1156 p = mutt_get_parameter ("charset", b->parameter);
1159 mutt_canonical_charset (d, dlen, NONULL (p));
1161 strfcpy (d, "us-ascii", dlen);
1167 /* Assumes called from send mode where BODY->filename points to actual file */
1168 void mutt_update_encoding (BODY * a)
1171 char chsbuff[STRING];
1173 /* override noconv when it's us-ascii */
1174 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1177 if (!a->force_charset && !a->noconv)
1178 mutt_delete_parameter ("charset", &a->parameter);
1180 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1183 mutt_set_encoding (a, info);
1184 mutt_stamp_attachment (a);
1186 mem_free (&a->content);
1191 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1193 char buffer[LONG_STRING];
1196 int cmflags, chflags;
1197 int pgp = WithCrypto ? hdr->security : 0;
1200 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1201 (hdr->security & ENCRYPT)) {
1202 if (!crypt_valid_passphrase (hdr->security))
1207 mutt_mktemp (buffer);
1208 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1211 body = mutt_new_body ();
1212 body->type = TYPEMESSAGE;
1213 body->subtype = str_dup ("rfc822");
1214 body->filename = str_dup (buffer);
1217 body->disposition = DISPINLINE;
1220 mutt_parse_mime_message (ctx, hdr);
1225 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1226 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1227 chflags |= CH_MIME | CH_TXTPLAIN;
1228 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1229 if ((WithCrypto & APPLICATION_PGP))
1231 if ((WithCrypto & APPLICATION_SMIME))
1232 pgp &= ~SMIMEENCRYPT;
1234 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1235 if ((WithCrypto & APPLICATION_PGP)
1236 && mutt_is_multipart_encrypted (hdr->content)) {
1237 chflags |= CH_MIME | CH_NONEWLINE;
1238 cmflags = M_CM_DECODE_PGP;
1241 else if ((WithCrypto & APPLICATION_PGP)
1242 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1243 chflags |= CH_MIME | CH_TXTPLAIN;
1244 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1247 else if ((WithCrypto & APPLICATION_SMIME)
1248 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1249 chflags |= CH_MIME | CH_TXTPLAIN;
1250 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1251 pgp &= ~SMIMEENCRYPT;
1255 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1260 body->hdr = mutt_new_header ();
1261 body->hdr->offset = 0;
1262 /* we don't need the user headers here */
1263 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1265 body->hdr->security = pgp;
1266 mutt_update_encoding (body);
1267 body->parts = body->hdr->content;
1274 BODY *mutt_make_file_attach (const char *path)
1279 att = mutt_new_body ();
1280 att->filename = str_dup (path);
1282 /* Attempt to determine the appropriate content-type based on the filename
1289 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1290 path)) != TYPEOTHER || *xbuf != '\0') {
1292 att->subtype = str_dup (buf);
1293 att->xtype = str_dup (xbuf);
1298 mutt_lookup_mime_type (att, path);
1302 if ((info = mutt_get_content_info (path, att)) == NULL) {
1303 mutt_free_body (&att);
1307 if (!att->subtype) {
1308 if (info->lobin == 0
1309 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1311 * Statistically speaking, there should be more than 10% "lobin"
1312 * chars if this is really a binary file...
1314 att->type = TYPETEXT;
1315 att->subtype = str_dup ("plain");
1318 att->type = TYPEAPPLICATION;
1319 att->subtype = str_dup ("octet-stream");
1323 mutt_update_encoding (att);
1327 static int get_toplevel_encoding (BODY * a)
1331 for (; a; a = a->next) {
1332 if (a->encoding == ENCBINARY)
1334 else if (a->encoding == ENC8BIT)
1341 BODY *mutt_make_multipart (BODY * b)
1345 new = mutt_new_body ();
1346 new->type = TYPEMULTIPART;
1347 new->subtype = str_dup ("mixed");
1348 new->encoding = get_toplevel_encoding (b);
1349 mutt_generate_boundary (&new->parameter);
1351 new->disposition = DISPINLINE;
1357 /* remove the multipart body if it exists */
1358 BODY *mutt_remove_multipart (BODY * b)
1366 mutt_free_body (&t);
1371 char *mutt_make_date (char *s, size_t len)
1373 time_t t = time (NULL);
1374 struct tm *l = localtime (&t);
1375 time_t tz = mutt_local_tz (t);
1379 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1380 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1381 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1382 (int) tz / 60, (int) abs (tz) % 60);
1386 /* wrapper around mutt_write_address() so we can handle very large
1387 recipient lists without needing a huge temporary buffer in memory */
1388 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1392 char buf[LONG_STRING];
1400 rfc822_write_address (buf, sizeof (buf), adr, display);
1401 len = str_len (buf);
1402 if (count && linelen + len > 74) {
1404 linelen = len + 8; /* tab is usually about 8 spaces... */
1407 if (count && adr->mailbox) {
1415 if (!adr->group && adr->next && adr->next->mailbox) {
1425 /* arbitrary number of elements to grow the array by */
1430 /* need to write the list in reverse because they are stored in reverse order
1431 * when parsed to speed up threading
1433 void mutt_write_references (LIST * r, FILE * f)
1436 int refcnt = 0, refmax = 0;
1438 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1439 if (refcnt == refmax)
1440 mem_realloc (&ref, (refmax += REF_INC) * sizeof (LIST *));
1444 while (refcnt-- > 0) {
1446 fputs (ref[refcnt]->data, f);
1452 /* Note: all RFC2047 encoding should be done outside of this routine, except
1453 * for the "real name." This will allow this routine to be used more than
1454 * once, if necessary.
1456 * Likewise, all IDN processing should happen outside of this routine.
1458 * mode == 1 => "lite" mode (used for edit_hdrs)
1459 * mode == 0 => normal mode. write full header + MIME headers
1460 * mode == -1 => write just the envelope info (used for postponing messages)
1462 * privacy != 0 => will omit any headers which may identify the user.
1463 * Output generated is suitable for being sent through
1464 * anonymous remailer chains.
1468 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1469 int mode, int privacy)
1471 char buffer[LONG_STRING];
1473 LIST *tmp = env->userhdrs;
1474 int has_agent = 0; /* user defined user-agent header field exists */
1477 if (!option (OPTNEWSSEND))
1479 if (mode == 0 && !privacy)
1480 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1482 /* OPTUSEFROM is not consulted here so that we can still write a From:
1483 * field if the user sets it with the `my_hdr' command
1485 if (env->from && !privacy) {
1487 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1488 fprintf (fp, "From: %s\n", buffer);
1493 mutt_write_address_list (env->to, fp, 4, 0);
1497 if (!option (OPTNEWSSEND))
1499 fputs ("To: \n", fp);
1503 mutt_write_address_list (env->cc, fp, 4, 0);
1507 if (!option (OPTNEWSSEND))
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 fputs ("Bcc: \n", fp);
1524 if (env->newsgroups)
1525 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1526 else if (mode == 1 && option (OPTNEWSSEND))
1527 fputs ("Newsgroups: \n", fp);
1529 if (env->followup_to)
1530 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1531 else if (mode == 1 && option (OPTNEWSSEND))
1532 fputs ("Followup-To: \n", fp);
1534 if (env->x_comment_to)
1535 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1536 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO))
1537 fputs ("X-Comment-To: \n", fp);
1541 fprintf (fp, "Subject: %s\n", env->subject);
1543 fputs ("Subject: \n", fp);
1545 /* save message id if the user has set it */
1546 if (env->message_id && !privacy)
1547 fprintf (fp, "Message-ID: %s\n", env->message_id);
1549 if (env->reply_to) {
1550 fputs ("Reply-To: ", fp);
1551 mutt_write_address_list (env->reply_to, fp, 10, 0);
1554 fputs ("Reply-To: \n", fp);
1556 if (env->mail_followup_to)
1558 if (!option (OPTNEWSSEND))
1561 fputs ("Mail-Followup-To: ", fp);
1562 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1566 if (env->references) {
1567 fputs ("References:", fp);
1568 mutt_write_references (env->references, fp);
1572 /* Add the MIME headers */
1573 fputs ("Mime-Version: 1.0\n", fp);
1574 mutt_write_mime_header (attach, fp);
1577 if (env->in_reply_to) {
1578 fputs ("In-Reply-To:", fp);
1579 mutt_write_references (env->in_reply_to, fp);
1583 /* Add any user defined headers */
1584 for (; tmp; tmp = tmp->next) {
1585 if ((p = strchr (tmp->data, ':'))) {
1589 continue; /* don't emit empty fields. */
1591 /* check to see if the user has overridden the user-agent field */
1592 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1598 fputs (tmp->data, fp);
1603 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1607 if (OperatingSystem != NULL) {
1608 os = OperatingSystem;
1611 if (uname (&un) == -1) {
1618 /* Add a vanity header */
1619 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1622 return (ferror (fp) == 0 ? 0 : -1);
1625 static void encode_headers (LIST * h)
1631 for (; h; h = h->next) {
1632 if (!(p = strchr (h->data, ':')))
1643 rfc2047_encode_string (&tmp);
1644 mem_realloc (&h->data,
1645 str_len (h->data) + 2 + str_len (tmp) + 1);
1647 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1653 const char *mutt_fqdn (short may_hide_host)
1657 if (Fqdn && Fqdn[0] != '@') {
1660 if (may_hide_host && option (OPTHIDDENHOST)) {
1661 if ((p = strchr (Fqdn, '.')))
1664 /* sanity check: don't hide the host if
1665 * the fqdn is something like detebe.org.
1668 if (!p || !(q = strchr (p, '.')))
1676 static char mutt_normalized_char (char c)
1680 if (strchr (".!#$%&'*+-/=?^_`{|}~", c))
1682 return '.'; /* normalized character (we're stricter than RFC2822, 3.6.4) */
1685 static void mutt_gen_localpart (char *buf, unsigned int len, char *fmt)
1689 char tmp[SHORT_STRING];
1696 for (; *fmt; ++fmt) {
1702 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mday);
1703 str_ncat (buf, len, tmp, 2);
1706 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_hour);
1707 str_ncat (buf, len, tmp, 2);
1710 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mon + 1);
1711 str_ncat (buf, len, tmp, 2);
1714 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_min);
1715 str_ncat (buf, len, tmp, 2);
1718 snprintf (tmp, sizeof (tmp), "%lo", (unsigned long) now);
1719 str_ncat (buf, len, tmp, str_len (tmp));
1722 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) getpid ());
1723 str_ncat (buf, len, tmp, str_len (tmp));
1726 snprintf (tmp, sizeof (tmp), "%c", MsgIdPfx);
1727 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1728 str_ncat (buf, len, tmp, 1);
1731 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) rand ());
1732 str_ncat (buf, len, tmp, str_len (tmp));
1735 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) rand ());
1736 str_ncat (buf, len, tmp, str_len (tmp));
1739 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_sec);
1740 str_ncat (buf, len, tmp, 2);
1743 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) now);
1744 str_ncat (buf, len, tmp, str_len (tmp));
1747 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) now);
1748 str_ncat (buf, len, tmp, str_len (tmp));
1751 snprintf (tmp, sizeof (tmp), "%04d", tm->tm_year + 1900); /* this will break in the year 10000 ;-) */
1752 str_ncat (buf, len, tmp, 4);
1755 str_ncat (buf, len, "%", 1);
1758 str_ncat (buf, len, ".", 1); /* invalid formats are replaced by '.' */
1765 c = mutt_normalized_char (*fmt); /* @todo: filter out invalid characters */
1766 str_ncat (buf, len, &c, 1);
1771 char *mutt_gen_msgid (void)
1773 char buf[SHORT_STRING];
1774 char localpart[SHORT_STRING];
1775 unsigned int localpart_length;
1778 if (!(fqdn = mutt_fqdn (0)))
1779 fqdn = NONULL (Hostname);
1781 localpart_length = sizeof (buf) - str_len (fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1783 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1785 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1786 return (str_dup (buf));
1789 static RETSIGTYPE alarm_handler (int sig)
1794 /* invoke sendmail in a subshell
1795 path (in) path to program to execute
1796 args (in) arguments to pass to program
1797 msg (in) temp file containing message to send
1798 tempfile (out) if sendmail is put in the background, this points
1799 to the temporary file containing the stdout of the
1802 send_msg (const char *path, char **args, const char *msg, char **tempfile)
1808 mutt_block_signals_system ();
1811 /* we also don't want to be stopped right now */
1812 sigaddset (&set, SIGTSTP);
1813 sigprocmask (SIG_BLOCK, &set, NULL);
1815 if (SendmailWait >= 0) {
1816 char tmp[_POSIX_PATH_MAX];
1819 *tempfile = str_dup (tmp);
1822 if ((pid = fork ()) == 0) {
1823 struct sigaction act, oldalrm;
1825 /* save parent's ID before setsid() */
1828 /* we want the delivery to continue even after the main process dies,
1829 * so we put ourselves into another session right away
1833 /* next we close all open files */
1834 #if defined(OPEN_MAX)
1835 for (fd = 0; fd < OPEN_MAX; fd++)
1837 #elif defined(_POSIX_OPEN_MAX)
1838 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1846 /* now the second fork() */
1847 if ((pid = fork ()) == 0) {
1848 /* "msg" will be opened as stdin */
1849 if (open (msg, O_RDONLY, 0) < 0) {
1855 if (SendmailWait >= 0) {
1856 /* *tempfile will be opened as stdout */
1857 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1860 /* redirect stderr to *tempfile too */
1865 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1867 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1874 else if (pid == -1) {
1876 mem_free (tempfile);
1880 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1881 * SendmailWait = 0: wait forever
1882 * SendmailWait < 0: don't wait
1884 if (SendmailWait > 0) {
1886 act.sa_handler = alarm_handler;
1888 /* need to make sure waitpid() is interrupted on SIGALRM */
1889 act.sa_flags = SA_INTERRUPT;
1893 sigemptyset (&act.sa_mask);
1894 sigaction (SIGALRM, &act, &oldalrm);
1895 alarm (SendmailWait);
1897 else if (SendmailWait < 0)
1898 _exit (0xff & EX_OK);
1900 if (waitpid (pid, &st, 0) > 0) {
1901 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1902 if (SendmailWait && st == (0xff & EX_OK)) {
1903 unlink (*tempfile); /* no longer needed */
1904 mem_free (tempfile);
1908 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1909 if (SendmailWait > 0) {
1911 mem_free (tempfile);
1915 /* reset alarm; not really needed, but... */
1917 sigaction (SIGALRM, &oldalrm, NULL);
1919 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1920 /* the parent is already dead */
1922 mem_free (tempfile);
1928 sigprocmask (SIG_UNBLOCK, &set, NULL);
1930 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1931 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1933 st = S_ERR; /* error */
1935 mutt_unblock_signals_system (1);
1940 static char **add_args (char **args, size_t * argslen, size_t * argsmax,
1943 for (; addr; addr = addr->next) {
1944 /* weed out group mailboxes, since those are for display only */
1945 if (addr->mailbox && !addr->group) {
1946 if (*argslen == *argsmax)
1947 mem_realloc (&args, (*argsmax += 5) * sizeof (char *));
1948 args[(*argslen)++] = addr->mailbox;
1954 static char **add_option (char **args, size_t * argslen, size_t * argsmax,
1957 if (*argslen == *argsmax)
1958 mem_realloc (&args, (*argsmax += 5) * sizeof (char *));
1959 args[(*argslen)++] = s;
1963 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
1964 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
1965 const char *msg, /* file containing message */
1967 { /* message contains 8bit chars */
1968 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1970 size_t argslen = 0, argsmax = 0;
1974 if (option (OPTNEWSSEND)) {
1975 char cmd[LONG_STRING];
1977 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1980 i = nntp_post (msg);
1989 s = str_dup (Sendmail);
1993 while ((ps = strtok (ps, " "))) {
1994 if (argslen == argsmax)
1995 mem_realloc (&args, sizeof (char *) * (argsmax += 5));
1998 args[argslen++] = ps;
2000 path = str_dup (ps);
2001 ps = strrchr (ps, '/');
2006 args[argslen++] = ps;
2013 if (!option (OPTNEWSSEND)) {
2015 if (eightbit && option (OPTUSE8BITMIME))
2016 args = add_option (args, &argslen, &argsmax, "-B8BITMIME");
2018 if (option (OPTENVFROM) && from && !from->next) {
2019 args = add_option (args, &argslen, &argsmax, "-f");
2020 args = add_args (args, &argslen, &argsmax, from);
2023 args = add_option (args, &argslen, &argsmax, "-N");
2024 args = add_option (args, &argslen, &argsmax, DsnNotify);
2027 args = add_option (args, &argslen, &argsmax, "-R");
2028 args = add_option (args, &argslen, &argsmax, DsnReturn);
2030 args = add_option (args, &argslen, &argsmax, "--");
2031 args = add_args (args, &argslen, &argsmax, to);
2032 args = add_args (args, &argslen, &argsmax, cc);
2033 args = add_args (args, &argslen, &argsmax, bcc);
2038 if (argslen == argsmax)
2039 mem_realloc (&args, sizeof (char *) * (++argsmax));
2041 args[argslen++] = NULL;
2043 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2045 const char *e = mutt_strsysexit (i);
2047 e = mutt_strsysexit (i);
2048 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2053 if (stat (childout, &st) == 0 && st.st_size > 0)
2054 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2062 mem_free (&childout);
2067 if (i == (EX_OK & 0xff))
2069 else if (i == S_BKG)
2076 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2077 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2078 const char *msg, /* file containing message */
2080 { /* message contains 8bit chars */
2083 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2086 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2089 /* appends string 'b' to string 'a', and returns the pointer to the new
2091 char *mutt_append_string (char *a, const char *b)
2093 size_t la = str_len (a);
2095 mem_realloc (&a, la + str_len (b) + 1);
2096 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2100 /* returns 1 if char `c' needs to be quoted to protect from shell
2101 interpretation when executing commands in a subshell */
2102 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2104 /* returns 1 if string `s' contains characters which could cause problems
2105 when used on a command line to execute a command */
2106 int mutt_needs_quote (const char *s)
2109 if (INVALID_CHAR (*s))
2116 /* Quote a string to prevent shell escapes when this string is used on the
2117 command line to send mail. */
2118 char *mutt_quote_string (const char *s)
2123 rlen = str_len (s) + 3;
2124 pr = r = (char *) mem_malloc (rlen);
2127 if (INVALID_CHAR (*s)) {
2130 mem_realloc (&r, ++rlen);
2141 /* For postponing (!final) do the necessary encodings only */
2142 void mutt_prepare_envelope (ENVELOPE * env, int final)
2144 char buffer[LONG_STRING];
2147 if (env->bcc && !(env->to || env->cc)) {
2148 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2149 * recipients if there is no To: or Cc: field, so attempt to suppress
2150 * it by using an empty To: field.
2152 env->to = rfc822_new_address ();
2154 env->to->next = rfc822_new_address ();
2157 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2160 env->to->mailbox = str_dup (buffer);
2163 mutt_set_followup_to (env);
2165 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2166 env->message_id = mutt_gen_msgid ();
2169 /* Take care of 8-bit => 7-bit conversion. */
2170 rfc2047_encode_adrlist (env->to, "To");
2171 rfc2047_encode_adrlist (env->cc, "Cc");
2172 rfc2047_encode_adrlist (env->bcc, "Bcc");
2173 rfc2047_encode_adrlist (env->from, "From");
2174 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2175 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2179 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2182 rfc2047_encode_string (&env->subject);
2184 encode_headers (env->userhdrs);
2187 void mutt_unprepare_envelope (ENVELOPE * env)
2191 for (item = env->userhdrs; item; item = item->next)
2192 rfc2047_decode (&item->data);
2194 rfc822_free_address (&env->mail_followup_to);
2196 /* back conversions */
2197 rfc2047_decode_adrlist (env->to);
2198 rfc2047_decode_adrlist (env->cc);
2199 rfc2047_decode_adrlist (env->bcc);
2200 rfc2047_decode_adrlist (env->from);
2201 rfc2047_decode_adrlist (env->reply_to);
2202 rfc2047_decode (&env->subject);
2205 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2206 const char *resent_from, ADDRESS * env_from)
2210 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2211 MESSAGE *msg = NULL;
2214 /* Try to bounce each message out, aborting if we get any failures. */
2215 for (i = 0; i < Context->msgcount; i++)
2216 if (Context->hdrs[i]->tagged)
2218 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2223 /* If we failed to open a message, return with error */
2224 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2230 mutt_mktemp (tempfile);
2231 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2232 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2234 if (!option (OPTBOUNCEDELIVERED))
2235 ch_flags |= CH_WEED_DELIVERED;
2237 fseek (fp, h->offset, 0);
2238 fprintf (f, "Resent-From: %s", resent_from);
2239 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2240 if (MsgIdFormat && *MsgIdFormat)
2241 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2242 fputs ("Resent-To: ", f);
2243 mutt_write_address_list (to, f, 11, 0);
2244 mutt_copy_header (fp, h, f, ch_flags, NULL);
2246 mutt_copy_bytes (fp, f, h->content->length);
2249 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2250 h->content->encoding == ENC8BIT);
2254 mx_close_message (&msg);
2259 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2262 const char *fqdn = mutt_fqdn (1);
2263 char resent_from[STRING];
2267 resent_from[0] = '\0';
2268 from = mutt_default_from ();
2271 rfc822_qualify (from, fqdn);
2273 rfc2047_encode_adrlist (from, "Resent-From");
2274 if (mutt_addrlist_to_idna (from, &err)) {
2275 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2278 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2281 unset_option (OPTNEWSSEND);
2284 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2286 rfc822_free_address (&from);
2292 /* given a list of addresses, return a list of unique addresses */
2293 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2295 ADDRESS *top = addr;
2296 ADDRESS **last = ⊤
2301 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2302 if (tmp->mailbox && addr->mailbox &&
2303 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2310 debug_print (2, ("Removing %s\n", addr->mailbox));
2315 rfc822_free_address (&addr);
2328 static void set_noconv_flags (BODY * b, short flag)
2330 for (; b; b = b->next) {
2331 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2332 set_noconv_flags (b->parts, flag);
2333 else if (b->type == TYPETEXT && b->noconv) {
2335 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2337 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2342 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2343 int post, char *fcc)
2347 char tempfile[_POSIX_PATH_MAX];
2348 FILE *tempfp = NULL;
2352 set_noconv_flags (hdr->content, 1);
2354 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2355 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2359 /* We need to add a Content-Length field to avoid problems where a line in
2360 * the message body begins with "From "
2362 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2363 mutt_mktemp (tempfile);
2364 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2365 mutt_perror (tempfile);
2366 mx_close_mailbox (&f, NULL);
2371 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2372 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2373 mx_close_mailbox (&f, NULL);
2377 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2378 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2380 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2383 /* (postponment) if this was a reply of some sort, <msgid> contians the
2384 * Message-ID: of message replied to. Save it using a special X-Mutt-
2385 * header so it can be picked up if the message is recalled at a later
2386 * point in time. This will allow the message to be marked as replied if
2387 * the same mailbox is still open.
2390 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2392 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2393 * it can be picked up when the message is recalled
2396 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2397 fprintf (msg->fp, "Status: RO\n");
2401 /* (postponment) if the mail is to be signed or encrypted, save this info */
2402 if ((WithCrypto & APPLICATION_PGP)
2403 && post && (hdr->security & APPLICATION_PGP)) {
2404 fputs ("X-Mutt-PGP: ", msg->fp);
2405 if (hdr->security & ENCRYPT)
2406 fputc ('E', msg->fp);
2407 if (hdr->security & SIGN) {
2408 fputc ('S', msg->fp);
2409 if (PgpSignAs && *PgpSignAs)
2410 fprintf (msg->fp, "<%s>", PgpSignAs);
2412 if (hdr->security & INLINE)
2413 fputc ('I', msg->fp);
2414 fputc ('\n', msg->fp);
2417 /* (postponment) if the mail is to be signed or encrypted, save this info */
2418 if ((WithCrypto & APPLICATION_SMIME)
2419 && post && (hdr->security & APPLICATION_SMIME)) {
2420 fputs ("X-Mutt-SMIME: ", msg->fp);
2421 if (hdr->security & ENCRYPT) {
2422 fputc ('E', msg->fp);
2423 if (SmimeCryptAlg && *SmimeCryptAlg)
2424 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2426 if (hdr->security & SIGN) {
2427 fputc ('S', msg->fp);
2428 if (SmimeDefaultKey && *SmimeDefaultKey)
2429 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2431 if (hdr->security & INLINE)
2432 fputc ('I', msg->fp);
2433 fputc ('\n', msg->fp);
2437 /* (postponement) if the mail is to be sent through a mixmaster
2438 * chain, save that information
2441 if (post && hdr->chain && hdr->chain) {
2444 fputs ("X-Mutt-Mix:", msg->fp);
2445 for (p = hdr->chain; p; p = p->next)
2446 fprintf (msg->fp, " %s", (char *) p->data);
2448 fputc ('\n', msg->fp);
2453 char sasha[LONG_STRING];
2456 mutt_write_mime_body (hdr->content, tempfp);
2458 /* make sure the last line ends with a newline. Emacs doesn't ensure
2459 * this will happen, and it can cause problems parsing the mailbox
2462 fseek (tempfp, -1, 2);
2463 if (fgetc (tempfp) != '\n') {
2464 fseek (tempfp, 0, 2);
2465 fputc ('\n', tempfp);
2469 if (ferror (tempfp)) {
2470 debug_print (1, ("%s: write failed.\n", tempfile));
2473 mx_commit_message (msg, &f); /* XXX - really? */
2474 mx_close_message (&msg);
2475 mx_close_mailbox (&f, NULL);
2479 /* count the number of lines */
2481 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2483 fprintf (msg->fp, "Content-Length: %ld\n", (long) ftell (tempfp));
2484 fprintf (msg->fp, "Lines: %d\n\n", lines);
2486 /* copy the body and clean up */
2488 r = mutt_copy_stream (tempfp, msg->fp);
2489 if (fclose (tempfp) != 0)
2491 /* if there was an error, leave the temp version */
2496 fputc ('\n', msg->fp); /* finish off the header */
2497 r = mutt_write_mime_body (hdr->content, msg->fp);
2500 if (mx_commit_message (msg, &f) != 0)
2502 mx_close_message (&msg);
2503 mx_close_mailbox (&f, NULL);
2506 set_noconv_flags (hdr->content, 0);