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.
17 #include "mutt_curses.h"
25 #include "mutt_crypt.h"
26 #include "mutt_idna.h"
31 #include "lib/debug.h"
42 #include <sys/utsname.h>
45 # include "mutt_libesmtp.h"
46 #endif /* USE_LIBESMTP */
52 #ifdef HAVE_SYSEXITS_H
54 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
58 /* If you are debugging this file, comment out the following line. */
67 extern char RFC822Specials[];
69 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
71 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
74 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
75 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
76 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
77 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
81 static char MsgIdPfx = 'A';
83 static void transform_to_7bit (BODY * a, FILE * fpin);
85 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
88 char line[77], savechar;
90 while ((c = fgetconv (fc)) != EOF) {
91 /* Wrap the line if needed. */
92 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
93 /* If the last character is "quoted", then be sure to move all three
94 * characters to the next line. Otherwise, just move the last
97 if (line[linelen - 3] == '=') {
98 line[linelen - 3] = 0;
103 line[1] = line[linelen - 2];
104 line[2] = line[linelen - 1];
108 savechar = line[linelen - 1];
109 line[linelen - 1] = '=';
118 /* Escape lines that begin with/only contain "the message separator". */
119 if (linelen == 4 && !safe_strncmp ("From", line, 4)) {
120 strfcpy (line, "=46rom", sizeof (line));
123 else if (linelen == 4 && !safe_strncmp ("from", line, 4)) {
124 strfcpy (line, "=66rom", sizeof (line));
127 else if (linelen == 1 && line[0] == '.') {
128 strfcpy (line, "=2E", sizeof (line));
133 if (c == '\n' && istext) {
134 /* Check to make sure there is no trailing space on this line. */
136 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
138 sprintf (line + linelen - 1, "=%2.2X",
139 (unsigned char) line[linelen - 1]);
143 int savechar = line[linelen - 1];
145 line[linelen - 1] = '=';
148 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
158 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
159 /* Check to make sure there is enough room for the quoted character.
160 * If not, wrap to the next line.
163 line[linelen++] = '=';
169 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
173 /* Don't worry about wrapping the line here. That will happen during
174 * the next iteration when I'll also know what the next character is.
180 /* Take care of anything left in the buffer */
182 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
183 /* take care of trailing whitespace */
185 sprintf (line + linelen - 1, "=%2.2X",
186 (unsigned char) line[linelen - 1]);
188 savechar = line[linelen - 1];
189 line[linelen - 1] = '=';
193 sprintf (line, "=%2.2X", (unsigned char) savechar);
202 static char b64_buffer[3];
203 static short b64_num;
204 static short b64_linelen;
206 static void b64_flush (FILE * fout)
213 if (b64_linelen >= 72) {
218 for (i = b64_num; i < 3; i++)
219 b64_buffer[i] = '\0';
221 fputc (B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
224 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
229 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
233 fputc (B64Chars[b64_buffer[2] & 0x3f], fout);
238 while (b64_linelen % 4) {
247 static void b64_putc (char c, FILE * fout)
252 b64_buffer[b64_num++] = c;
256 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
260 b64_num = b64_linelen = 0;
262 while ((ch = fgetconv (fc)) != EOF) {
263 if (istext && ch == '\n' && ch1 != '\r')
264 b64_putc ('\r', fout);
272 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
276 while ((ch = fgetconv (fc)) != EOF)
281 int mutt_write_mime_header (BODY * a, FILE * f)
291 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
294 len = 25 + mutt_strlen (a->subtype); /* approximate len. of content-type */
296 for (p = a->parameter; p; p = p->next) {
305 tmp = safe_strdup (p->value);
306 encode = rfc2231_encode_string (&tmp);
307 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
309 /* Dirty hack to make messages readable by Outlook Express
310 * for the Mac: force quotes around the boundary parameter
311 * even when they aren't needed.
314 if (!ascii_strcasecmp (p->attribute, "boundary")
315 && !strcmp (buffer, tmp))
316 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
320 tmplen = mutt_strlen (buffer) + mutt_strlen (p->attribute) + 1;
322 if (len + tmplen + 2 > 76) {
331 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
339 fprintf (f, "Content-Description: %s\n", a->description);
341 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
344 if (!(fn = a->d_filename))
350 /* Strip off the leading path... */
351 if ((t = strrchr (fn, '/')))
357 tmp = safe_strdup (t);
358 encode = rfc2231_encode_string (&tmp);
359 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
361 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
367 if (a->encoding != ENC7BIT)
368 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
370 /* Do NOT add the terminator here!!! */
371 return (ferror (f) ? -1 : 0);
374 # define write_as_text_part(a) (mutt_is_text_part(a) \
375 || ((WithCrypto & APPLICATION_PGP)\
376 && mutt_is_application_pgp(a)))
378 int mutt_write_mime_body (BODY * a, FILE * f)
380 char *p, boundary[SHORT_STRING];
381 char send_charset[SHORT_STRING];
386 if (a->type == TYPEMULTIPART) {
387 /* First, find the boundary to use */
388 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
389 debug_print (1, ("no boundary parameter found!\n"));
390 mutt_error _("No boundary parameter found! [report this error]");
394 strfcpy (boundary, p, sizeof (boundary));
396 for (t = a->parts; t; t = t->next) {
397 fprintf (f, "\n--%s\n", boundary);
398 if (mutt_write_mime_header (t, f) == -1)
401 if (mutt_write_mime_body (t, f) == -1)
404 fprintf (f, "\n--%s--\n", boundary);
405 return (ferror (f) ? -1 : 0);
408 /* This is pretty gross, but it's the best solution for now... */
409 if ((WithCrypto & APPLICATION_PGP)
410 && a->type == TYPEAPPLICATION
411 && mutt_strcmp (a->subtype, "pgp-encrypted") == 0) {
412 fputs ("Version: 1\n", f);
416 if ((fpin = fopen (a->filename, "r")) == NULL) {
417 debug_print (1, ("%s no longer exists!\n", a->filename));
418 mutt_error (_("%s no longer exists!"), a->filename);
422 if (a->type == TYPETEXT && (!a->noconv))
423 fc = fgetconv_open (fpin, a->file_charset,
424 mutt_get_body_charset (send_charset,
425 sizeof (send_charset), a), 0);
427 fc = fgetconv_open (fpin, 0, 0, 0);
429 if (a->encoding == ENCQUOTEDPRINTABLE)
430 encode_quoted (fc, f, write_as_text_part (a));
431 else if (a->encoding == ENCBASE64)
432 encode_base64 (fc, f, write_as_text_part (a));
433 else if (a->type == TYPETEXT && (!a->noconv))
434 encode_8bit (fc, f, write_as_text_part (a));
436 mutt_copy_stream (fpin, f);
438 fgetconv_close (&fc);
441 return (ferror (f) ? -1 : 0);
444 #undef write_as_text_part
446 #define BOUNDARYLEN 16
447 void mutt_generate_boundary (PARAMETER ** parm)
449 char rs[BOUNDARYLEN + 1];
454 for (i = 0; i < BOUNDARYLEN; i++)
455 *p++ = B64Chars[LRAND () % sizeof (B64Chars)];
458 mutt_set_parameter ("boundary", rs, parm);
470 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
474 int whitespace = s->whitespace;
476 int linelen = s->linelen;
477 int was_cr = s->was_cr;
479 if (!d) { /* This signals EOF */
482 if (linelen > info->linemax)
483 info->linemax = linelen;
488 for (; dlen; d++, dlen--) {
501 if (linelen > info->linemax)
502 info->linemax = linelen;
517 if (linelen > info->linemax)
518 info->linemax = linelen;
523 else if (ch == '\r') {
531 else if (ch == '\t' || ch == '\f') {
535 else if (ch < 32 || ch == 127)
539 if ((ch == 'F') || (ch == 'f'))
549 if (linelen == 2 && ch != 'r')
551 else if (linelen == 3 && ch != 'o')
553 else if (linelen == 4) {
566 if (ch != ' ' && ch != '\t')
571 s->whitespace = whitespace;
573 s->linelen = linelen;
578 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
579 #define BUGGY_ICONV 1
582 * Find the best charset conversion of the file from fromcode into one
583 * of the tocodes. If successful, set *tocode and CONTENT *info and
584 * return the number of characters converted inexactly. If no
585 * conversion was possible, return -1.
587 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
588 * which would otherwise prevent us from knowing the number of inexact
589 * conversions. Where the candidate target charset is UTF-8 we avoid
590 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
591 * fails with some libraries.
593 * We assume that the output from iconv is never more than 4 times as
594 * long as the input for any pair of charsets we might be interested
597 static size_t convert_file_to (FILE * file, const char *fromcode,
598 int ncodes, const char **tocodes,
599 int *tocode, CONTENT * info)
603 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
604 ICONV_CONST char *ib, *ub;
606 size_t ibl, obl, ubl, ubl1, n, ret;
609 CONTENT_STATE *states;
612 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
613 if (cd1 == (iconv_t) (-1))
616 cd = safe_calloc (ncodes, sizeof (iconv_t));
617 score = safe_calloc (ncodes, sizeof (size_t));
618 states = safe_calloc (ncodes, sizeof (CONTENT_STATE));
619 infos = safe_calloc (ncodes, sizeof (CONTENT));
621 for (i = 0; i < ncodes; i++)
622 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
623 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
625 /* Special case for conversion to UTF-8 */
626 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
632 /* Try to fill input buffer */
633 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
636 /* Convert to UTF-8 */
638 ob = bufu, obl = sizeof (bufu);
639 n = iconv (cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
640 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
641 if (n == (size_t) (-1) &&
642 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
643 assert (errno == EILSEQ ||
644 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
650 /* Convert from UTF-8 */
651 for (i = 0; i < ncodes; i++)
652 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
653 ub = bufu, ubl = ubl1;
654 ob = bufo, obl = sizeof (bufo);
655 n = iconv (cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
656 if (n == (size_t) (-1)) {
657 assert (errno == E2BIG ||
658 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
659 score[i] = (size_t) (-1);
663 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
666 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
667 /* Special case for conversion to UTF-8 */
668 update_content_info (&infos[i], &states[i], bufu, ubl1);
671 /* Save unused input */
672 memmove (bufi, ib, ibl);
673 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
680 /* Find best score */
682 for (i = 0; i < ncodes; i++) {
683 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
684 /* Special case for conversion to UTF-8 */
689 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
691 else if (ret == (size_t) (-1) || score[i] < ret) {
698 if (ret != (size_t) (-1)) {
699 memcpy (info, &infos[*tocode], sizeof (CONTENT));
700 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
704 for (i = 0; i < ncodes; i++)
705 if (cd[i] != (iconv_t) (-1))
717 #endif /* !HAVE_ICONV */
721 * Find the first of the fromcodes that gives a valid conversion and
722 * the best charset conversion of the file into one of the tocodes. If
723 * successful, set *fromcode and *tocode to dynamically allocated
724 * strings, set CONTENT *info, and return the number of characters
725 * converted inexactly. If no conversion was possible, return -1.
727 * Both fromcodes and tocodes may be colon-separated lists of charsets.
728 * However, if fromcode is zero then fromcodes is assumed to be the
729 * name of a single charset even if it contains a colon.
731 static size_t convert_file_from_to (FILE * file,
732 const char *fromcodes,
733 const char *tocodes, char **fromcode,
734 char **tocode, CONTENT * info)
742 /* Count the tocodes */
744 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
745 if ((c1 = strchr (c, ':')) == c)
751 tcode = safe_malloc (ncodes * sizeof (char *));
752 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
753 if ((c1 = strchr (c, ':')) == c)
755 tcode[i] = str_substrdup (c, c1);
760 /* Try each fromcode in turn */
761 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
762 if ((c1 = strchr (c, ':')) == c)
764 fcode = str_substrdup (c, c1);
766 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
768 if (ret != (size_t) (-1)) {
778 /* There is only one fromcode */
779 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
781 if (ret != (size_t) (-1)) {
788 for (i = 0; i < ncodes; i++)
797 * Analyze the contents of a file to determine which MIME encoding to use.
798 * Also set the body charset, sometimes, or not.
800 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
816 if (stat (fname, &sb) == -1) {
817 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
821 if (!S_ISREG (sb.st_mode)) {
822 mutt_error (_("%s isn't a regular file."), fname);
826 if ((fp = fopen (fname, "r")) == NULL) {
827 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
831 info = safe_calloc (1, sizeof (CONTENT));
832 memset (&state, 0, sizeof (state));
834 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
835 char *chs = mutt_get_parameter ("charset", b->parameter);
836 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
837 FileCharset : Charset) : Charset;
838 if (Charset && (chs || SendCharset) &&
839 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
840 &fromcode, &tocode, info) != (size_t) (-1)) {
842 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
843 mutt_set_parameter ("charset", chsbuf, &b->parameter);
845 b->file_charset = fromcode;
853 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
854 update_content_info (info, &state, buffer, r);
855 update_content_info (info, &state, 0, 0);
859 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
860 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
862 && !mutt_is_us_ascii (Charset) ? Charset :
863 "unknown-8bit"), &b->parameter);
868 /* Given a file with path ``s'', see if there is a registered MIME type.
869 * returns the major MIME type, and copies the subtype to ``d''. First look
870 * for ~/.mime.types, then look in a system mime.types if we can find one.
871 * The longest match is used so that we can match `ps.gz' when `gz' also
875 int mutt_lookup_mime_type (BODY * att, const char *path)
879 char buf[LONG_STRING];
880 char subtype[STRING], xtype[STRING];
882 int szf, sze, cur_sze;
890 szf = mutt_strlen (path);
892 for (count = 0; count < 3; count++) {
894 * can't use strtok() because we use it in an inner loop below, so use
895 * a switch statement here instead.
899 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
902 strfcpy (buf, SYSCONFDIR "/muttng-mime.types", sizeof (buf));
905 strfcpy (buf, PKGDATADIR "/mime.types", sizeof (buf));
908 debug_print (1, ("Internal error, count = %d.\n", count));
909 goto bye; /* shouldn't happen */
912 if ((f = fopen (buf, "r")) != NULL) {
913 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
914 /* weed out any comments */
915 if ((p = strchr (buf, '#')))
918 /* remove any leading space. */
922 /* position on the next field in this line */
923 if ((p = strpbrk (ct, " \t")) == NULL)
928 /* cycle through the file extensions */
929 while ((p = strtok (p, " \t\n"))) {
930 sze = mutt_strlen (p);
931 if ((sze > cur_sze) && (szf >= sze) &&
932 (safe_strcasecmp (path + szf - sze, p) == 0
933 || ascii_strcasecmp (path + szf - sze, p) == 0) && (szf == sze
940 /* get the content-type */
942 if ((p = strchr (ct, '/')) == NULL) {
943 /* malformed line, just skip it. */
948 for (q = p; *q && !ISSPACE (*q); q++);
950 str_substrcpy (subtype, p, q, sizeof (subtype));
952 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
953 strfcpy (xtype, ct, sizeof (xtype));
966 if (type != TYPEOTHER || *xtype != '\0') {
968 str_replace (&att->subtype, subtype);
969 str_replace (&att->xtype, xtype);
975 void mutt_message_to_7bit (BODY * a, FILE * fp)
977 char temp[_POSIX_PATH_MAX];
983 if (!a->filename && fp)
985 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
986 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
991 if (stat (a->filename, &sb) == -1) {
992 mutt_perror ("stat");
995 a->length = sb.st_size;
999 if (!(fpout = safe_fopen (temp, "w+"))) {
1000 mutt_perror ("fopen");
1004 fseek (fpin, a->offset, 0);
1005 a->parts = mutt_parse_messageRFC822 (fpin, a);
1007 transform_to_7bit (a->parts, fpin);
1009 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1010 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1012 fputs ("Mime-Version: 1.0\n", fpout);
1013 mutt_write_mime_header (a->parts, fpout);
1014 fputc ('\n', fpout);
1015 mutt_write_mime_body (a->parts, fpout);
1027 a->encoding = ENC7BIT;
1028 a->d_filename = a->filename;
1029 if (a->filename && a->unlink)
1030 unlink (a->filename);
1031 a->filename = safe_strdup (temp);
1033 if (stat (a->filename, &sb) == -1) {
1034 mutt_perror ("stat");
1037 a->length = sb.st_size;
1038 mutt_free_body (&a->parts);
1039 a->hdr->content = NULL;
1042 static void transform_to_7bit (BODY * a, FILE * fpin)
1044 char buff[_POSIX_PATH_MAX];
1048 memset (&s, 0, sizeof (s));
1049 for (; a; a = a->next) {
1050 if (a->type == TYPEMULTIPART) {
1051 if (a->encoding != ENC7BIT)
1052 a->encoding = ENC7BIT;
1054 transform_to_7bit (a->parts, fpin);
1056 else if (mutt_is_message_type (a->type, a->subtype)) {
1057 mutt_message_to_7bit (a, fpin);
1061 a->force_charset = 1;
1064 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1065 mutt_perror ("fopen");
1069 mutt_decode_attachment (a, &s);
1071 a->d_filename = a->filename;
1072 a->filename = safe_strdup (buff);
1074 if (stat (a->filename, &sb) == -1) {
1075 mutt_perror ("stat");
1078 a->length = sb.st_size;
1080 mutt_update_encoding (a);
1081 if (a->encoding == ENC8BIT)
1082 a->encoding = ENCQUOTEDPRINTABLE;
1083 else if (a->encoding == ENCBINARY)
1084 a->encoding = ENCBASE64;
1089 /* determine which Content-Transfer-Encoding to use */
1090 static void mutt_set_encoding (BODY * b, CONTENT * info)
1092 char send_charset[SHORT_STRING];
1094 if (b->type == TYPETEXT) {
1096 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1097 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1098 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1099 b->encoding = ENCQUOTEDPRINTABLE;
1100 else if (info->hibin)
1101 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1103 b->encoding = ENC7BIT;
1105 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1106 if (info->lobin || info->hibin) {
1107 if (option (OPTALLOW8BIT) && !info->lobin)
1108 b->encoding = ENC8BIT;
1110 mutt_message_to_7bit (b, NULL);
1113 b->encoding = ENC7BIT;
1115 else if (b->type == TYPEAPPLICATION
1116 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1117 b->encoding = ENC7BIT;
1120 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1121 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1124 /* Determine which encoding is smaller */
1125 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1126 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1127 b->encoding = ENCBASE64;
1129 b->encoding = ENCQUOTEDPRINTABLE;
1133 b->encoding = ENC7BIT;
1137 void mutt_stamp_attachment (BODY * a)
1139 a->stamp = time (NULL);
1142 /* Get a body's character set */
1144 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1148 if (b && b->type != TYPETEXT)
1152 p = mutt_get_parameter ("charset", b->parameter);
1155 mutt_canonical_charset (d, dlen, NONULL (p));
1157 strfcpy (d, "us-ascii", dlen);
1163 /* Assumes called from send mode where BODY->filename points to actual file */
1164 void mutt_update_encoding (BODY * a)
1167 char chsbuff[STRING];
1169 /* override noconv when it's us-ascii */
1170 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1173 if (!a->force_charset && !a->noconv)
1174 mutt_delete_parameter ("charset", &a->parameter);
1176 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1179 mutt_set_encoding (a, info);
1180 mutt_stamp_attachment (a);
1187 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1189 char buffer[LONG_STRING];
1192 int cmflags, chflags;
1193 int pgp = WithCrypto ? hdr->security : 0;
1196 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1197 (hdr->security & ENCRYPT)) {
1198 if (!crypt_valid_passphrase (hdr->security))
1203 mutt_mktemp (buffer);
1204 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1207 body = mutt_new_body ();
1208 body->type = TYPEMESSAGE;
1209 body->subtype = safe_strdup ("rfc822");
1210 body->filename = safe_strdup (buffer);
1213 body->disposition = DISPINLINE;
1216 mutt_parse_mime_message (ctx, hdr);
1221 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1222 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1223 chflags |= CH_MIME | CH_TXTPLAIN;
1224 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1225 if ((WithCrypto & APPLICATION_PGP))
1227 if ((WithCrypto & APPLICATION_SMIME))
1228 pgp &= ~SMIMEENCRYPT;
1230 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1231 if ((WithCrypto & APPLICATION_PGP)
1232 && mutt_is_multipart_encrypted (hdr->content)) {
1233 chflags |= CH_MIME | CH_NONEWLINE;
1234 cmflags = M_CM_DECODE_PGP;
1237 else if ((WithCrypto & APPLICATION_PGP)
1238 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1239 chflags |= CH_MIME | CH_TXTPLAIN;
1240 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1243 else if ((WithCrypto & APPLICATION_SMIME)
1244 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1245 chflags |= CH_MIME | CH_TXTPLAIN;
1246 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1247 pgp &= ~SMIMEENCRYPT;
1251 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1256 body->hdr = mutt_new_header ();
1257 body->hdr->offset = 0;
1258 /* we don't need the user headers here */
1259 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1261 body->hdr->security = pgp;
1262 mutt_update_encoding (body);
1263 body->parts = body->hdr->content;
1270 BODY *mutt_make_file_attach (const char *path)
1275 att = mutt_new_body ();
1276 att->filename = safe_strdup (path);
1278 /* Attempt to determine the appropriate content-type based on the filename
1285 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1286 path)) != TYPEOTHER || *xbuf != '\0') {
1288 att->subtype = safe_strdup (buf);
1289 att->xtype = safe_strdup (xbuf);
1294 mutt_lookup_mime_type (att, path);
1298 if ((info = mutt_get_content_info (path, att)) == NULL) {
1299 mutt_free_body (&att);
1303 if (!att->subtype) {
1304 if (info->lobin == 0
1305 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1307 * Statistically speaking, there should be more than 10% "lobin"
1308 * chars if this is really a binary file...
1310 att->type = TYPETEXT;
1311 att->subtype = safe_strdup ("plain");
1314 att->type = TYPEAPPLICATION;
1315 att->subtype = safe_strdup ("octet-stream");
1319 mutt_update_encoding (att);
1323 static int get_toplevel_encoding (BODY * a)
1327 for (; a; a = a->next) {
1328 if (a->encoding == ENCBINARY)
1330 else if (a->encoding == ENC8BIT)
1337 BODY *mutt_make_multipart (BODY * b)
1341 new = mutt_new_body ();
1342 new->type = TYPEMULTIPART;
1343 new->subtype = safe_strdup ("mixed");
1344 new->encoding = get_toplevel_encoding (b);
1345 mutt_generate_boundary (&new->parameter);
1347 new->disposition = DISPINLINE;
1353 /* remove the multipart body if it exists */
1354 BODY *mutt_remove_multipart (BODY * b)
1362 mutt_free_body (&t);
1367 char *mutt_make_date (char *s, size_t len)
1369 time_t t = time (NULL);
1370 struct tm *l = localtime (&t);
1371 time_t tz = mutt_local_tz (t);
1375 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1376 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1377 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1378 (int) tz / 60, (int) abs (tz) % 60);
1382 /* wrapper around mutt_write_address() so we can handle very large
1383 recipient lists without needing a huge temporary buffer in memory */
1384 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1388 char buf[LONG_STRING];
1396 rfc822_write_address (buf, sizeof (buf), adr, display);
1397 len = mutt_strlen (buf);
1398 if (count && linelen + len > 74) {
1400 linelen = len + 8; /* tab is usually about 8 spaces... */
1403 if (count && adr->mailbox) {
1411 if (!adr->group && adr->next && adr->next->mailbox) {
1421 /* arbitrary number of elements to grow the array by */
1426 /* need to write the list in reverse because they are stored in reverse order
1427 * when parsed to speed up threading
1429 void mutt_write_references (LIST * r, FILE * f)
1432 int refcnt = 0, refmax = 0;
1434 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1435 if (refcnt == refmax)
1436 safe_realloc (&ref, (refmax += REF_INC) * sizeof (LIST *));
1440 while (refcnt-- > 0) {
1442 fputs (ref[refcnt]->data, f);
1448 /* Note: all RFC2047 encoding should be done outside of this routine, except
1449 * for the "real name." This will allow this routine to be used more than
1450 * once, if necessary.
1452 * Likewise, all IDN processing should happen outside of this routine.
1454 * mode == 1 => "lite" mode (used for edit_hdrs)
1455 * mode == 0 => normal mode. write full header + MIME headers
1456 * mode == -1 => write just the envelope info (used for postponing messages)
1458 * privacy != 0 => will omit any headers which may identify the user.
1459 * Output generated is suitable for being sent through
1460 * anonymous remailer chains.
1464 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1465 int mode, int privacy)
1467 char buffer[LONG_STRING];
1469 LIST *tmp = env->userhdrs;
1470 int has_agent = 0; /* user defined user-agent header field exists */
1473 if (!option (OPTNEWSSEND))
1475 if (mode == 0 && !privacy)
1476 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1478 /* OPTUSEFROM is not consulted here so that we can still write a From:
1479 * field if the user sets it with the `my_hdr' command
1481 if (env->from && !privacy) {
1483 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1484 fprintf (fp, "From: %s\n", buffer);
1489 mutt_write_address_list (env->to, fp, 4, 0);
1493 if (!option (OPTNEWSSEND))
1495 fputs ("To: \n", fp);
1499 mutt_write_address_list (env->cc, fp, 4, 0);
1503 if (!option (OPTNEWSSEND))
1505 fputs ("Cc: \n", fp);
1508 if (mode != 0 || option (OPTWRITEBCC)) {
1509 fputs ("Bcc: ", fp);
1510 mutt_write_address_list (env->bcc, fp, 5, 0);
1515 if (!option (OPTNEWSSEND))
1517 fputs ("Bcc: \n", fp);
1520 if (env->newsgroups)
1521 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1522 else if (mode == 1 && option (OPTNEWSSEND))
1523 fputs ("Newsgroups: \n", fp);
1525 if (env->followup_to)
1526 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1527 else if (mode == 1 && option (OPTNEWSSEND))
1528 fputs ("Followup-To: \n", fp);
1530 if (env->x_comment_to)
1531 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1532 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO))
1533 fputs ("X-Comment-To: \n", fp);
1537 fprintf (fp, "Subject: %s\n", env->subject);
1539 fputs ("Subject: \n", fp);
1541 /* save message id if the user has set it */
1542 if (env->message_id && !privacy)
1543 fprintf (fp, "Message-ID: %s\n", env->message_id);
1545 if (env->reply_to) {
1546 fputs ("Reply-To: ", fp);
1547 mutt_write_address_list (env->reply_to, fp, 10, 0);
1550 fputs ("Reply-To: \n", fp);
1552 if (env->mail_followup_to)
1554 if (!option (OPTNEWSSEND))
1557 fputs ("Mail-Followup-To: ", fp);
1558 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1562 if (env->references) {
1563 fputs ("References:", fp);
1564 mutt_write_references (env->references, fp);
1568 /* Add the MIME headers */
1569 fputs ("Mime-Version: 1.0\n", fp);
1570 mutt_write_mime_header (attach, fp);
1573 if (env->in_reply_to) {
1574 fputs ("In-Reply-To:", fp);
1575 mutt_write_references (env->in_reply_to, fp);
1579 /* Add any user defined headers */
1580 for (; tmp; tmp = tmp->next) {
1581 if ((p = strchr (tmp->data, ':'))) {
1585 continue; /* don't emit empty fields. */
1587 /* check to see if the user has overridden the user-agent field */
1588 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1594 fputs (tmp->data, fp);
1599 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1603 if (OperatingSystem != NULL) {
1604 os = OperatingSystem;
1607 if (uname (&un) == -1) {
1614 /* Add a vanity header */
1615 fprintf (fp, "User-Agent: mutt-ng/%s (%s)\n", MUTT_VERSION, os);
1618 return (ferror (fp) == 0 ? 0 : -1);
1621 static void encode_headers (LIST * h)
1627 for (; h; h = h->next) {
1628 if (!(p = strchr (h->data, ':')))
1634 tmp = safe_strdup (p);
1639 rfc2047_encode_string (&tmp);
1640 safe_realloc (&h->data,
1641 mutt_strlen (h->data) + 2 + mutt_strlen (tmp) + 1);
1643 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1649 const char *mutt_fqdn (short may_hide_host)
1653 if (Fqdn && Fqdn[0] != '@') {
1656 if (may_hide_host && option (OPTHIDDENHOST)) {
1657 if ((p = strchr (Fqdn, '.')))
1660 /* sanity check: don't hide the host if
1661 * the fqdn is something like detebe.org.
1664 if (!p || !(q = strchr (p, '.')))
1672 static char mutt_normalized_char (char c)
1676 if (strchr (".!#$%&'*+-/=?^_`{|}~", c))
1678 return '.'; /* normalized character (we're stricter than RFC2822, 3.6.4) */
1681 static void mutt_gen_localpart (char *buf, unsigned int len, char *fmt)
1685 char tmp[SHORT_STRING];
1692 for (; *fmt; ++fmt) {
1698 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mday);
1699 safe_strncat (buf, len, tmp, 2);
1702 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_hour);
1703 safe_strncat (buf, len, tmp, 2);
1706 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mon + 1);
1707 safe_strncat (buf, len, tmp, 2);
1710 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_min);
1711 safe_strncat (buf, len, tmp, 2);
1714 snprintf (tmp, sizeof (tmp), "%lo", (unsigned long) now);
1715 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1718 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) getpid ());
1719 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1722 snprintf (tmp, sizeof (tmp), "%c", MsgIdPfx);
1723 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1724 safe_strncat (buf, len, tmp, 1);
1727 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) rand ());
1728 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1731 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) rand ());
1732 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1735 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_sec);
1736 safe_strncat (buf, len, tmp, 2);
1739 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) now);
1740 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1743 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) now);
1744 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1747 snprintf (tmp, sizeof (tmp), "%04d", tm->tm_year + 1900); /* this will break in the year 10000 ;-) */
1748 safe_strncat (buf, len, tmp, 4);
1751 safe_strncat (buf, len, "%", 1);
1754 safe_strncat (buf, len, ".", 1); /* invalid formats are replaced by '.' */
1761 c = mutt_normalized_char (*fmt); /* @todo: filter out invalid characters */
1762 safe_strncat (buf, len, &c, 1);
1767 char *mutt_gen_msgid (void)
1769 char buf[SHORT_STRING];
1770 char localpart[SHORT_STRING];
1771 unsigned int localpart_length;
1774 if (!(fqdn = mutt_fqdn (0)))
1775 fqdn = NONULL (Hostname);
1777 localpart_length = sizeof (buf) - mutt_strlen (fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1779 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1781 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1782 return (safe_strdup (buf));
1785 static RETSIGTYPE alarm_handler (int sig)
1790 /* invoke sendmail in a subshell
1791 path (in) path to program to execute
1792 args (in) arguments to pass to program
1793 msg (in) temp file containing message to send
1794 tempfile (out) if sendmail is put in the background, this points
1795 to the temporary file containing the stdout of the
1798 send_msg (const char *path, char **args, const char *msg, char **tempfile)
1804 mutt_block_signals_system ();
1807 /* we also don't want to be stopped right now */
1808 sigaddset (&set, SIGTSTP);
1809 sigprocmask (SIG_BLOCK, &set, NULL);
1811 if (SendmailWait >= 0) {
1812 char tmp[_POSIX_PATH_MAX];
1815 *tempfile = safe_strdup (tmp);
1818 if ((pid = fork ()) == 0) {
1819 struct sigaction act, oldalrm;
1821 /* save parent's ID before setsid() */
1824 /* we want the delivery to continue even after the main process dies,
1825 * so we put ourselves into another session right away
1829 /* next we close all open files */
1830 #if defined(OPEN_MAX)
1831 for (fd = 0; fd < OPEN_MAX; fd++)
1833 #elif defined(_POSIX_OPEN_MAX)
1834 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1842 /* now the second fork() */
1843 if ((pid = fork ()) == 0) {
1844 /* "msg" will be opened as stdin */
1845 if (open (msg, O_RDONLY, 0) < 0) {
1851 if (SendmailWait >= 0) {
1852 /* *tempfile will be opened as stdout */
1853 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1856 /* redirect stderr to *tempfile too */
1861 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1863 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1870 else if (pid == -1) {
1876 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1877 * SendmailWait = 0: wait forever
1878 * SendmailWait < 0: don't wait
1880 if (SendmailWait > 0) {
1882 act.sa_handler = alarm_handler;
1884 /* need to make sure waitpid() is interrupted on SIGALRM */
1885 act.sa_flags = SA_INTERRUPT;
1889 sigemptyset (&act.sa_mask);
1890 sigaction (SIGALRM, &act, &oldalrm);
1891 alarm (SendmailWait);
1893 else if (SendmailWait < 0)
1894 _exit (0xff & EX_OK);
1896 if (waitpid (pid, &st, 0) > 0) {
1897 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1898 if (SendmailWait && st == (0xff & EX_OK)) {
1899 unlink (*tempfile); /* no longer needed */
1904 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1905 if (SendmailWait > 0) {
1911 /* reset alarm; not really needed, but... */
1913 sigaction (SIGALRM, &oldalrm, NULL);
1915 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1916 /* the parent is already dead */
1924 sigprocmask (SIG_UNBLOCK, &set, NULL);
1926 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1927 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1929 st = S_ERR; /* error */
1931 mutt_unblock_signals_system (1);
1936 static char **add_args (char **args, size_t * argslen, size_t * argsmax,
1939 for (; addr; addr = addr->next) {
1940 /* weed out group mailboxes, since those are for display only */
1941 if (addr->mailbox && !addr->group) {
1942 if (*argslen == *argsmax)
1943 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
1944 args[(*argslen)++] = addr->mailbox;
1950 static char **add_option (char **args, size_t * argslen, size_t * argsmax,
1953 if (*argslen == *argsmax)
1954 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
1955 args[(*argslen)++] = s;
1959 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
1960 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
1961 const char *msg, /* file containing message */
1963 { /* message contains 8bit chars */
1964 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1966 size_t argslen = 0, argsmax = 0;
1970 if (option (OPTNEWSSEND)) {
1971 char cmd[LONG_STRING];
1973 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1976 i = nntp_post (msg);
1981 s = safe_strdup (cmd);
1985 s = safe_strdup (Sendmail);
1989 while ((ps = strtok (ps, " "))) {
1990 if (argslen == argsmax)
1991 safe_realloc (&args, sizeof (char *) * (argsmax += 5));
1994 args[argslen++] = ps;
1996 path = safe_strdup (ps);
1997 ps = strrchr (ps, '/');
2002 args[argslen++] = ps;
2009 if (!option (OPTNEWSSEND)) {
2011 if (eightbit && option (OPTUSE8BITMIME))
2012 args = add_option (args, &argslen, &argsmax, "-B8BITMIME");
2014 if (option (OPTENVFROM) && from && !from->next) {
2015 args = add_option (args, &argslen, &argsmax, "-f");
2016 args = add_args (args, &argslen, &argsmax, from);
2019 args = add_option (args, &argslen, &argsmax, "-N");
2020 args = add_option (args, &argslen, &argsmax, DsnNotify);
2023 args = add_option (args, &argslen, &argsmax, "-R");
2024 args = add_option (args, &argslen, &argsmax, DsnReturn);
2026 args = add_option (args, &argslen, &argsmax, "--");
2027 args = add_args (args, &argslen, &argsmax, to);
2028 args = add_args (args, &argslen, &argsmax, cc);
2029 args = add_args (args, &argslen, &argsmax, bcc);
2034 if (argslen == argsmax)
2035 safe_realloc (&args, sizeof (char *) * (++argsmax));
2037 args[argslen++] = NULL;
2039 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2041 const char *e = mutt_strsysexit (i);
2043 e = mutt_strsysexit (i);
2044 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2049 if (stat (childout, &st) == 0 && st.st_size > 0)
2050 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2063 if (i == (EX_OK & 0xff))
2065 else if (i == S_BKG)
2072 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2073 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2074 const char *msg, /* file containing message */
2076 { /* message contains 8bit chars */
2079 return mutt_invoke_libesmtp (from, to, cc, bcc, msg, eightbit);
2082 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2085 /* appends string 'b' to string 'a', and returns the pointer to the new
2087 char *mutt_append_string (char *a, const char *b)
2089 size_t la = mutt_strlen (a);
2091 safe_realloc (&a, la + mutt_strlen (b) + 1);
2092 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2096 /* returns 1 if char `c' needs to be quoted to protect from shell
2097 interpretation when executing commands in a subshell */
2098 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2100 /* returns 1 if string `s' contains characters which could cause problems
2101 when used on a command line to execute a command */
2102 int mutt_needs_quote (const char *s)
2105 if (INVALID_CHAR (*s))
2112 /* Quote a string to prevent shell escapes when this string is used on the
2113 command line to send mail. */
2114 char *mutt_quote_string (const char *s)
2119 rlen = mutt_strlen (s) + 3;
2120 pr = r = (char *) safe_malloc (rlen);
2123 if (INVALID_CHAR (*s)) {
2126 safe_realloc (&r, ++rlen);
2137 /* For postponing (!final) do the necessary encodings only */
2138 void mutt_prepare_envelope (ENVELOPE * env, int final)
2140 char buffer[LONG_STRING];
2143 if (env->bcc && !(env->to || env->cc)) {
2144 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2145 * recipients if there is no To: or Cc: field, so attempt to suppress
2146 * it by using an empty To: field.
2148 env->to = rfc822_new_address ();
2150 env->to->next = rfc822_new_address ();
2153 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2156 env->to->mailbox = safe_strdup (buffer);
2159 mutt_set_followup_to (env);
2161 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2162 env->message_id = mutt_gen_msgid ();
2165 /* Take care of 8-bit => 7-bit conversion. */
2166 rfc2047_encode_adrlist (env->to, "To");
2167 rfc2047_encode_adrlist (env->cc, "Cc");
2168 rfc2047_encode_adrlist (env->bcc, "Bcc");
2169 rfc2047_encode_adrlist (env->from, "From");
2170 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2171 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2175 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2178 rfc2047_encode_string (&env->subject);
2180 encode_headers (env->userhdrs);
2183 void mutt_unprepare_envelope (ENVELOPE * env)
2187 for (item = env->userhdrs; item; item = item->next)
2188 rfc2047_decode (&item->data);
2190 rfc822_free_address (&env->mail_followup_to);
2192 /* back conversions */
2193 rfc2047_decode_adrlist (env->to);
2194 rfc2047_decode_adrlist (env->cc);
2195 rfc2047_decode_adrlist (env->bcc);
2196 rfc2047_decode_adrlist (env->from);
2197 rfc2047_decode_adrlist (env->reply_to);
2198 rfc2047_decode (&env->subject);
2201 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2202 const char *resent_from, ADDRESS * env_from)
2206 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2207 MESSAGE *msg = NULL;
2210 /* Try to bounce each message out, aborting if we get any failures. */
2211 for (i = 0; i < Context->msgcount; i++)
2212 if (Context->hdrs[i]->tagged)
2214 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2219 /* If we failed to open a message, return with error */
2220 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2226 mutt_mktemp (tempfile);
2227 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2228 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2230 if (!option (OPTBOUNCEDELIVERED))
2231 ch_flags |= CH_WEED_DELIVERED;
2233 fseek (fp, h->offset, 0);
2234 fprintf (f, "Resent-From: %s", resent_from);
2235 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2236 if (MsgIdFormat && *MsgIdFormat)
2237 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2238 fputs ("Resent-To: ", f);
2239 mutt_write_address_list (to, f, 11, 0);
2240 mutt_copy_header (fp, h, f, ch_flags, NULL);
2242 mutt_copy_bytes (fp, f, h->content->length);
2245 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2246 h->content->encoding == ENC8BIT);
2250 mx_close_message (&msg);
2255 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2258 const char *fqdn = mutt_fqdn (1);
2259 char resent_from[STRING];
2263 resent_from[0] = '\0';
2264 from = mutt_default_from ();
2267 rfc822_qualify (from, fqdn);
2269 rfc2047_encode_adrlist (from, "Resent-From");
2270 if (mutt_addrlist_to_idna (from, &err)) {
2271 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2274 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2277 unset_option (OPTNEWSSEND);
2280 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2282 rfc822_free_address (&from);
2288 /* given a list of addresses, return a list of unique addresses */
2289 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2291 ADDRESS *top = addr;
2292 ADDRESS **last = ⊤
2297 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2298 if (tmp->mailbox && addr->mailbox &&
2299 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2306 debug_print (2, ("Removing %s\n", addr->mailbox));
2311 rfc822_free_address (&addr);
2324 static void set_noconv_flags (BODY * b, short flag)
2326 for (; b; b = b->next) {
2327 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2328 set_noconv_flags (b->parts, flag);
2329 else if (b->type == TYPETEXT && b->noconv) {
2331 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2333 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2338 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2339 int post, char *fcc)
2343 char tempfile[_POSIX_PATH_MAX];
2344 FILE *tempfp = NULL;
2348 set_noconv_flags (hdr->content, 1);
2350 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2351 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2355 /* We need to add a Content-Length field to avoid problems where a line in
2356 * the message body begins with "From "
2358 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2359 mutt_mktemp (tempfile);
2360 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2361 mutt_perror (tempfile);
2362 mx_close_mailbox (&f, NULL);
2367 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2368 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2369 mx_close_mailbox (&f, NULL);
2373 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2374 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2376 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2379 /* (postponment) if this was a reply of some sort, <msgid> contians the
2380 * Message-ID: of message replied to. Save it using a special X-Mutt-
2381 * header so it can be picked up if the message is recalled at a later
2382 * point in time. This will allow the message to be marked as replied if
2383 * the same mailbox is still open.
2386 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2388 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2389 * it can be picked up when the message is recalled
2392 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2393 fprintf (msg->fp, "Status: RO\n");
2397 /* (postponment) if the mail is to be signed or encrypted, save this info */
2398 if ((WithCrypto & APPLICATION_PGP)
2399 && post && (hdr->security & APPLICATION_PGP)) {
2400 fputs ("X-Mutt-PGP: ", msg->fp);
2401 if (hdr->security & ENCRYPT)
2402 fputc ('E', msg->fp);
2403 if (hdr->security & SIGN) {
2404 fputc ('S', msg->fp);
2405 if (PgpSignAs && *PgpSignAs)
2406 fprintf (msg->fp, "<%s>", PgpSignAs);
2408 if (hdr->security & INLINE)
2409 fputc ('I', msg->fp);
2410 fputc ('\n', msg->fp);
2413 /* (postponment) if the mail is to be signed or encrypted, save this info */
2414 if ((WithCrypto & APPLICATION_SMIME)
2415 && post && (hdr->security & APPLICATION_SMIME)) {
2416 fputs ("X-Mutt-SMIME: ", msg->fp);
2417 if (hdr->security & ENCRYPT) {
2418 fputc ('E', msg->fp);
2419 if (SmimeCryptAlg && *SmimeCryptAlg)
2420 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2422 if (hdr->security & SIGN) {
2423 fputc ('S', msg->fp);
2424 if (SmimeDefaultKey && *SmimeDefaultKey)
2425 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2427 if (hdr->security & INLINE)
2428 fputc ('I', msg->fp);
2429 fputc ('\n', msg->fp);
2433 /* (postponement) if the mail is to be sent through a mixmaster
2434 * chain, save that information
2437 if (post && hdr->chain && hdr->chain) {
2440 fputs ("X-Mutt-Mix:", msg->fp);
2441 for (p = hdr->chain; p; p = p->next)
2442 fprintf (msg->fp, " %s", (char *) p->data);
2444 fputc ('\n', msg->fp);
2449 char sasha[LONG_STRING];
2452 mutt_write_mime_body (hdr->content, tempfp);
2454 /* make sure the last line ends with a newline. Emacs doesn't ensure
2455 * this will happen, and it can cause problems parsing the mailbox
2458 fseek (tempfp, -1, 2);
2459 if (fgetc (tempfp) != '\n') {
2460 fseek (tempfp, 0, 2);
2461 fputc ('\n', tempfp);
2465 if (ferror (tempfp)) {
2466 debug_print (1, ("%s: write failed.\n", tempfile));
2469 mx_commit_message (msg, &f); /* XXX - really? */
2470 mx_close_message (&msg);
2471 mx_close_mailbox (&f, NULL);
2475 /* count the number of lines */
2477 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2479 fprintf (msg->fp, "Content-Length: %ld\n", (long) ftell (tempfp));
2480 fprintf (msg->fp, "Lines: %d\n\n", lines);
2482 /* copy the body and clean up */
2484 r = mutt_copy_stream (tempfp, msg->fp);
2485 if (fclose (tempfp) != 0)
2487 /* if there was an error, leave the temp version */
2492 fputc ('\n', msg->fp); /* finish off the header */
2493 r = mutt_write_mime_body (hdr->content, msg->fp);
2496 if (mx_commit_message (msg, &f) != 0)
2498 mx_close_message (&msg);
2499 mx_close_mailbox (&f, NULL);
2502 set_noconv_flags (hdr->content, 0);