2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
5 * This file is part of mutt-ng, see http://www.muttng.org/.
6 * It's licensed under the GNU General Public License,
7 * please see the file GPL in the top level source directory.
16 #include <lib-lib/mem.h>
17 #include <lib-lib/ascii.h>
18 #include <lib-lib/str.h>
19 #include <lib-lib/macros.h>
20 #include <lib-lib/file.h>
24 #include "recvattach.h"
25 #include "mutt_curses.h"
33 #include "mutt_crypt.h"
34 #include "mutt_idna.h"
36 #include "lib/debug.h"
47 #include <sys/utsname.h>
50 # include "mutt_libesmtp.h"
51 #endif /* USE_LIBESMTP */
57 #ifdef HAVE_SYSEXITS_H
59 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
63 /* If you are debugging this file, comment out the following line. */
72 extern char RFC822Specials[];
74 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
76 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
79 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
80 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
81 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
82 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
86 static char MsgIdPfx = 'A';
88 static void transform_to_7bit (BODY * a, FILE * fpin);
90 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
93 char line[77], savechar;
95 while ((c = fgetconv (fc)) != EOF) {
96 /* Wrap the line if needed. */
97 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
98 /* If the last character is "quoted", then be sure to move all three
99 * characters to the next line. Otherwise, just move the last
102 if (line[linelen - 3] == '=') {
103 line[linelen - 3] = 0;
108 line[1] = line[linelen - 2];
109 line[2] = line[linelen - 1];
113 savechar = line[linelen - 1];
114 line[linelen - 1] = '=';
123 /* Escape lines that begin with/only contain "the message separator". */
124 if (linelen == 4 && !m_strncmp("From", line, 4)) {
125 m_strcpy(line, sizeof(line), "=46rom");
128 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
129 m_strcpy(line, sizeof(line), "=66rom");
132 else if (linelen == 1 && line[0] == '.') {
133 m_strcpy(line, sizeof(line), "=2E");
138 if (c == '\n' && istext) {
139 /* Check to make sure there is no trailing space on this line. */
141 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
143 sprintf (line + linelen - 1, "=%2.2X",
144 (unsigned char) line[linelen - 1]);
148 int savechar = line[linelen - 1];
150 line[linelen - 1] = '=';
153 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
163 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
164 /* Check to make sure there is enough room for the quoted character.
165 * If not, wrap to the next line.
168 line[linelen++] = '=';
174 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
178 /* Don't worry about wrapping the line here. That will happen during
179 * the next iteration when I'll also know what the next character is.
185 /* Take care of anything left in the buffer */
187 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
188 /* take care of trailing whitespace */
190 sprintf (line + linelen - 1, "=%2.2X",
191 (unsigned char) line[linelen - 1]);
193 savechar = line[linelen - 1];
194 line[linelen - 1] = '=';
198 sprintf (line, "=%2.2X", (unsigned char) savechar);
207 static char b64_buffer[3];
208 static short b64_num;
209 static short b64_linelen;
211 static void b64_flush (FILE * fout)
218 if (b64_linelen >= 72) {
223 for (i = b64_num; i < 3; i++)
224 b64_buffer[i] = '\0';
226 fputc (B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
229 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
234 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
238 fputc (B64Chars[b64_buffer[2] & 0x3f], fout);
243 while (b64_linelen % 4) {
252 static void b64_putc (char c, FILE * fout)
257 b64_buffer[b64_num++] = c;
261 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
265 b64_num = b64_linelen = 0;
267 while ((ch = fgetconv (fc)) != EOF) {
268 if (istext && ch == '\n' && ch1 != '\r')
269 b64_putc ('\r', fout);
277 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
281 while ((ch = fgetconv (fc)) != EOF)
286 int mutt_write_mime_header (BODY * a, FILE * f)
296 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
299 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
301 for (p = a->parameter; p; p = p->next) {
310 tmp = m_strdup(p->value);
311 encode = rfc2231_encode_string (&tmp);
312 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
314 /* Dirty hack to make messages readable by Outlook Express
315 * for the Mac: force quotes around the boundary parameter
316 * even when they aren't needed.
319 if (!ascii_strcasecmp (p->attribute, "boundary")
320 && !strcmp (buffer, tmp))
321 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
325 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
327 if (len + tmplen + 2 > 76) {
336 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
344 fprintf (f, "Content-Description: %s\n", a->description);
346 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
349 if (!(fn = a->d_filename))
355 /* Strip off the leading path... */
356 if ((t = strrchr (fn, '/')))
363 encode = rfc2231_encode_string (&tmp);
364 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
366 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
372 if (a->encoding != ENC7BIT)
373 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
375 /* Do NOT add the terminator here!!! */
376 return (ferror (f) ? -1 : 0);
379 # define write_as_text_part(a) (mutt_is_text_part(a) \
380 || ((WithCrypto & APPLICATION_PGP)\
381 && mutt_is_application_pgp(a)))
383 int mutt_write_mime_body (BODY * a, FILE * f)
385 char *p, boundary[SHORT_STRING];
386 char send_charset[SHORT_STRING];
391 if (a->type == TYPEMULTIPART) {
392 /* First, find the boundary to use */
393 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
394 debug_print (1, ("no boundary parameter found!\n"));
395 mutt_error _("No boundary parameter found! [report this error]");
399 m_strcpy(boundary, sizeof(boundary), p);
401 for (t = a->parts; t; t = t->next) {
402 fprintf (f, "\n--%s\n", boundary);
403 if (mutt_write_mime_header (t, f) == -1)
406 if (mutt_write_mime_body (t, f) == -1)
409 fprintf (f, "\n--%s--\n", boundary);
410 return (ferror (f) ? -1 : 0);
413 /* This is pretty gross, but it's the best solution for now... */
414 if ((WithCrypto & APPLICATION_PGP)
415 && a->type == TYPEAPPLICATION
416 && m_strcmp(a->subtype, "pgp-encrypted") == 0) {
417 fputs ("Version: 1\n", f);
421 if ((fpin = fopen (a->filename, "r")) == NULL) {
422 debug_print (1, ("%s no longer exists!\n", a->filename));
423 mutt_error (_("%s no longer exists!"), a->filename);
427 if (a->type == TYPETEXT && (!a->noconv))
428 fc = fgetconv_open (fpin, a->file_charset,
429 mutt_get_body_charset (send_charset,
430 sizeof (send_charset), a), 0);
432 fc = fgetconv_open (fpin, 0, 0, 0);
434 if (a->encoding == ENCQUOTEDPRINTABLE)
435 encode_quoted (fc, f, write_as_text_part (a));
436 else if (a->encoding == ENCBASE64)
437 encode_base64 (fc, f, write_as_text_part (a));
438 else if (a->type == TYPETEXT && (!a->noconv))
439 encode_8bit (fc, f, write_as_text_part (a));
441 mutt_copy_stream (fpin, f);
443 fgetconv_close (&fc);
446 return (ferror (f) ? -1 : 0);
449 #undef write_as_text_part
451 #define BOUNDARYLEN 16
452 void mutt_generate_boundary (PARAMETER ** parm)
454 char rs[BOUNDARYLEN + 1];
459 for (i = 0; i < BOUNDARYLEN; i++)
460 *p++ = B64Chars[LRAND () % sizeof (B64Chars)];
463 mutt_set_parameter ("boundary", rs, parm);
475 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
479 int whitespace = s->whitespace;
481 int linelen = s->linelen;
482 int was_cr = s->was_cr;
484 if (!d) { /* This signals EOF */
487 if (linelen > info->linemax)
488 info->linemax = linelen;
493 for (; dlen; d++, dlen--) {
506 if (linelen > info->linemax)
507 info->linemax = linelen;
522 if (linelen > info->linemax)
523 info->linemax = linelen;
528 else if (ch == '\r') {
536 else if (ch == '\t' || ch == '\f') {
540 else if (ch < 32 || ch == 127)
544 if ((ch == 'F') || (ch == 'f'))
554 if (linelen == 2 && ch != 'r')
556 else if (linelen == 3 && ch != 'o')
558 else if (linelen == 4) {
571 if (ch != ' ' && ch != '\t')
576 s->whitespace = whitespace;
578 s->linelen = linelen;
583 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
584 #define BUGGY_ICONV 1
587 * Find the best charset conversion of the file from fromcode into one
588 * of the tocodes. If successful, set *tocode and CONTENT *info and
589 * return the number of characters converted inexactly. If no
590 * conversion was possible, return -1.
592 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
593 * which would otherwise prevent us from knowing the number of inexact
594 * conversions. Where the candidate target charset is UTF-8 we avoid
595 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
596 * fails with some libraries.
598 * We assume that the output from iconv is never more than 4 times as
599 * long as the input for any pair of charsets we might be interested
602 static size_t convert_file_to (FILE * file, const char *fromcode,
603 int ncodes, const char **tocodes,
604 int *tocode, CONTENT * info)
608 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
611 size_t ibl, obl, ubl, ubl1, n, ret;
614 CONTENT_STATE *states;
617 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
618 if (cd1 == (iconv_t) (-1))
621 cd = p_new(iconv_t, ncodes);
622 score = p_new(size_t, ncodes);
623 states = p_new(CONTENT_STATE, ncodes);
624 infos = p_new(CONTENT, ncodes);
626 for (i = 0; i < ncodes; i++)
627 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
628 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
630 /* Special case for conversion to UTF-8 */
631 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
637 /* Try to fill input buffer */
638 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
641 /* Convert to UTF-8 */
643 ob = bufu, obl = sizeof (bufu);
644 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
645 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
646 if (n == (size_t) (-1) &&
647 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
648 assert (errno == EILSEQ ||
649 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
655 /* Convert from UTF-8 */
656 for (i = 0; i < ncodes; i++)
657 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
658 ub = bufu, ubl = ubl1;
659 ob = bufo, obl = sizeof (bufo);
660 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
661 if (n == (size_t) (-1)) {
662 assert (errno == E2BIG ||
663 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
664 score[i] = (size_t) (-1);
668 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
671 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
672 /* Special case for conversion to UTF-8 */
673 update_content_info (&infos[i], &states[i], bufu, ubl1);
676 /* Save unused input */
677 memmove (bufi, ib, ibl);
678 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
685 /* Find best score */
687 for (i = 0; i < ncodes; i++) {
688 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
689 /* Special case for conversion to UTF-8 */
694 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
696 else if (ret == (size_t) (-1) || score[i] < ret) {
703 if (ret != (size_t) (-1)) {
704 memcpy (info, &infos[*tocode], sizeof (CONTENT));
705 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
709 for (i = 0; i < ncodes; i++)
710 if (cd[i] != (iconv_t) (-1))
722 #endif /* !HAVE_ICONV */
726 * Find the first of the fromcodes that gives a valid conversion and
727 * the best charset conversion of the file into one of the tocodes. If
728 * successful, set *fromcode and *tocode to dynamically allocated
729 * strings, set CONTENT *info, and return the number of characters
730 * converted inexactly. If no conversion was possible, return -1.
732 * Both fromcodes and tocodes may be colon-separated lists of charsets.
733 * However, if fromcode is zero then fromcodes is assumed to be the
734 * name of a single charset even if it contains a colon.
736 static size_t convert_file_from_to (FILE * file,
737 const char *fromcodes,
738 const char *tocodes, char **fromcode,
739 char **tocode, CONTENT * info)
747 /* Count the tocodes */
749 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
750 if ((c1 = strchr (c, ':')) == c)
756 tcode = p_new(char *, ncodes);
757 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
758 if ((c1 = strchr (c, ':')) == c)
760 tcode[i] = str_substrdup (c, c1);
765 /* Try each fromcode in turn */
766 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
767 if ((c1 = strchr (c, ':')) == c)
769 fcode = str_substrdup (c, c1);
771 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
773 if (ret != (size_t) (-1)) {
783 /* There is only one fromcode */
784 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
786 if (ret != (size_t) (-1)) {
793 for (i = 0; i < ncodes; i++)
802 * Analyze the contents of a file to determine which MIME encoding to use.
803 * Also set the body charset, sometimes, or not.
805 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
810 char *fromcode = NULL;
821 if (stat (fname, &sb) == -1) {
822 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
826 if (!S_ISREG (sb.st_mode)) {
827 mutt_error (_("%s isn't a regular file."), fname);
831 if ((fp = fopen (fname, "r")) == NULL) {
832 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
836 info = p_new(CONTENT, 1);
839 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
840 char *chs = mutt_get_parameter ("charset", b->parameter);
841 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
842 FileCharset : Charset) : Charset;
843 if (Charset && (chs || SendCharset) &&
844 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
845 &fromcode, &tocode, info) != (size_t) (-1)) {
847 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
848 mutt_set_parameter ("charset", chsbuf, &b->parameter);
850 b->file_charset = fromcode;
858 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
859 update_content_info (info, &state, buffer, r);
860 update_content_info (info, &state, 0, 0);
864 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
865 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
867 && !mutt_is_us_ascii (Charset) ? Charset :
868 "unknown-8bit"), &b->parameter);
873 /* Given a file with path ``s'', see if there is a registered MIME type.
874 * returns the major MIME type, and copies the subtype to ``d''. First look
875 * for ~/.mime.types, then look in a system mime.types if we can find one.
876 * The longest match is used so that we can match `ps.gz' when `gz' also
880 int mutt_lookup_mime_type (BODY * att, const char *path)
884 char buf[LONG_STRING];
885 char subtype[STRING], xtype[STRING];
887 int szf, sze, cur_sze;
895 szf = m_strlen(path);
897 for (count = 0; count < 4; count++) {
899 * can't use strtok() because we use it in an inner loop below, so use
900 * a switch statement here instead.
904 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
907 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/muttng-mime.types");
910 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
913 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
916 debug_print (1, ("Internal error, count = %d.\n", count));
917 goto bye; /* shouldn't happen */
920 if ((f = fopen (buf, "r")) != NULL) {
921 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
922 /* weed out any comments */
923 if ((p = strchr (buf, '#')))
926 /* remove any leading space. */
927 ct = vskipspaces(buf);
929 /* position on the next field in this line */
930 if ((p = strpbrk (ct, " \t")) == NULL)
935 /* cycle through the file extensions */
936 while ((p = strtok (p, " \t\n"))) {
938 if ((sze > cur_sze) && (szf >= sze) &&
939 (m_strcasecmp(path + szf - sze, p) == 0
940 || ascii_strcasecmp (path + szf - sze, p) == 0) && (szf == sze
947 /* get the content-type */
949 if ((p = strchr (ct, '/')) == NULL) {
950 /* malformed line, just skip it. */
955 for (q = p; *q && !ISSPACE (*q); q++);
957 str_substrcpy (subtype, p, q, sizeof (subtype));
959 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
960 m_strcpy(xtype, sizeof(xtype), ct);
973 if (type != TYPEOTHER || *xtype != '\0') {
975 str_replace (&att->subtype, subtype);
976 str_replace (&att->xtype, xtype);
982 void mutt_message_to_7bit (BODY * a, FILE * fp)
984 char temp[_POSIX_PATH_MAX];
990 if (!a->filename && fp)
992 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
993 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
998 if (stat (a->filename, &sb) == -1) {
999 mutt_perror ("stat");
1002 a->length = sb.st_size;
1006 if (!(fpout = safe_fopen (temp, "w+"))) {
1007 mutt_perror ("fopen");
1011 fseeko (fpin, a->offset, 0);
1012 a->parts = mutt_parse_messageRFC822 (fpin, a);
1014 transform_to_7bit (a->parts, fpin);
1016 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1017 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1019 fputs ("MIME-Version: 1.0\n", fpout);
1020 mutt_write_mime_header (a->parts, fpout);
1021 fputc ('\n', fpout);
1022 mutt_write_mime_body (a->parts, fpout);
1034 a->encoding = ENC7BIT;
1035 a->d_filename = a->filename;
1036 if (a->filename && a->unlink)
1037 unlink (a->filename);
1038 a->filename = m_strdup(temp);
1040 if (stat (a->filename, &sb) == -1) {
1041 mutt_perror ("stat");
1044 a->length = sb.st_size;
1045 mutt_free_body (&a->parts);
1046 a->hdr->content = NULL;
1049 static void transform_to_7bit (BODY * a, FILE * fpin)
1051 char buff[_POSIX_PATH_MAX];
1056 for (; a; a = a->next) {
1057 if (a->type == TYPEMULTIPART) {
1058 if (a->encoding != ENC7BIT)
1059 a->encoding = ENC7BIT;
1061 transform_to_7bit (a->parts, fpin);
1063 else if (mutt_is_message_type (a->type, a->subtype)) {
1064 mutt_message_to_7bit (a, fpin);
1068 a->force_charset = 1;
1071 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1072 mutt_perror ("fopen");
1076 mutt_decode_attachment (a, &s);
1078 a->d_filename = a->filename;
1079 a->filename = m_strdup(buff);
1081 if (stat (a->filename, &sb) == -1) {
1082 mutt_perror ("stat");
1085 a->length = sb.st_size;
1087 mutt_update_encoding (a);
1088 if (a->encoding == ENC8BIT)
1089 a->encoding = ENCQUOTEDPRINTABLE;
1090 else if (a->encoding == ENCBINARY)
1091 a->encoding = ENCBASE64;
1096 /* determine which Content-Transfer-Encoding to use */
1097 static void mutt_set_encoding (BODY * b, CONTENT * info)
1099 char send_charset[SHORT_STRING];
1101 if (b->type == TYPETEXT) {
1103 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1104 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1105 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1106 b->encoding = ENCQUOTEDPRINTABLE;
1107 else if (info->hibin)
1108 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1110 b->encoding = ENC7BIT;
1112 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1113 if (info->lobin || info->hibin) {
1114 if (option (OPTALLOW8BIT) && !info->lobin)
1115 b->encoding = ENC8BIT;
1117 mutt_message_to_7bit (b, NULL);
1120 b->encoding = ENC7BIT;
1122 else if (b->type == TYPEAPPLICATION
1123 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1124 b->encoding = ENC7BIT;
1127 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1128 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1131 /* Determine which encoding is smaller */
1132 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1133 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1134 b->encoding = ENCBASE64;
1136 b->encoding = ENCQUOTEDPRINTABLE;
1140 b->encoding = ENC7BIT;
1144 void mutt_stamp_attachment (BODY * a)
1146 a->stamp = time (NULL);
1149 /* Get a body's character set */
1151 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1155 if (b && b->type != TYPETEXT)
1159 p = mutt_get_parameter ("charset", b->parameter);
1162 mutt_canonical_charset (d, dlen, NONULL (p));
1164 m_strcpy(d, dlen, "us-ascii");
1170 /* Assumes called from send mode where BODY->filename points to actual file */
1171 void mutt_update_encoding (BODY * a)
1174 char chsbuff[STRING];
1176 /* override noconv when it's us-ascii */
1177 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1180 if (!a->force_charset && !a->noconv)
1181 mutt_delete_parameter ("charset", &a->parameter);
1183 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1186 mutt_set_encoding (a, info);
1187 mutt_stamp_attachment (a);
1189 p_delete(&a->content);
1194 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1196 char buffer[LONG_STRING];
1199 int cmflags, chflags;
1200 int pgp = WithCrypto ? hdr->security : 0;
1203 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1204 (hdr->security & ENCRYPT)) {
1205 if (!crypt_valid_passphrase (hdr->security))
1210 mutt_mktemp (buffer);
1211 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1214 body = mutt_new_body ();
1215 body->type = TYPEMESSAGE;
1216 body->subtype = m_strdup("rfc822");
1217 body->filename = m_strdup(buffer);
1220 body->disposition = DISPINLINE;
1223 mutt_parse_mime_message (ctx, hdr);
1228 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1229 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1230 chflags |= CH_MIME | CH_TXTPLAIN;
1231 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1232 if ((WithCrypto & APPLICATION_PGP))
1234 if ((WithCrypto & APPLICATION_SMIME))
1235 pgp &= ~SMIMEENCRYPT;
1237 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1238 if ((WithCrypto & APPLICATION_PGP)
1239 && mutt_is_multipart_encrypted (hdr->content)) {
1240 chflags |= CH_MIME | CH_NONEWLINE;
1241 cmflags = M_CM_DECODE_PGP;
1244 else if ((WithCrypto & APPLICATION_PGP)
1245 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1246 chflags |= CH_MIME | CH_TXTPLAIN;
1247 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1250 else if ((WithCrypto & APPLICATION_SMIME)
1251 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1252 chflags |= CH_MIME | CH_TXTPLAIN;
1253 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1254 pgp &= ~SMIMEENCRYPT;
1258 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1263 body->hdr = mutt_new_header ();
1264 body->hdr->offset = 0;
1265 /* we don't need the user headers here */
1266 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1268 body->hdr->security = pgp;
1269 mutt_update_encoding (body);
1270 body->parts = body->hdr->content;
1277 BODY *mutt_make_file_attach (const char *path)
1282 att = mutt_new_body ();
1283 att->filename = m_strdup(path);
1285 /* Attempt to determine the appropriate content-type based on the filename
1292 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1293 path)) != TYPEOTHER || *xbuf != '\0') {
1295 att->subtype = m_strdup(buf);
1296 att->xtype = m_strdup(xbuf);
1301 mutt_lookup_mime_type (att, path);
1305 if ((info = mutt_get_content_info (path, att)) == NULL) {
1306 mutt_free_body (&att);
1310 if (!att->subtype) {
1311 if (info->lobin == 0
1312 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1314 * Statistically speaking, there should be more than 10% "lobin"
1315 * chars if this is really a binary file...
1317 att->type = TYPETEXT;
1318 att->subtype = m_strdup("plain");
1321 att->type = TYPEAPPLICATION;
1322 att->subtype = m_strdup("octet-stream");
1326 mutt_update_encoding (att);
1330 static int get_toplevel_encoding (BODY * a)
1334 for (; a; a = a->next) {
1335 if (a->encoding == ENCBINARY)
1337 else if (a->encoding == ENC8BIT)
1344 BODY *mutt_make_multipart (BODY * b)
1348 new = mutt_new_body ();
1349 new->type = TYPEMULTIPART;
1350 new->subtype = m_strdup("mixed");
1351 new->encoding = get_toplevel_encoding (b);
1352 mutt_generate_boundary (&new->parameter);
1354 new->disposition = DISPINLINE;
1360 /* remove the multipart body if it exists */
1361 BODY *mutt_remove_multipart (BODY * b)
1369 mutt_free_body (&t);
1374 char *mutt_make_date (char *s, size_t len)
1376 time_t t = time (NULL);
1377 struct tm *l = localtime (&t);
1378 time_t tz = mutt_local_tz (t);
1382 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1383 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1384 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1385 (int) tz / 60, (int) abs (tz) % 60);
1389 /* wrapper around mutt_write_address() so we can handle very large
1390 recipient lists without needing a huge temporary buffer in memory */
1391 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1395 char buf[LONG_STRING];
1403 rfc822_write_address (buf, sizeof (buf), adr, display);
1404 len = m_strlen(buf);
1405 if (count && linelen + len > 74) {
1407 linelen = len + 8; /* tab is usually about 8 spaces... */
1410 if (count && adr->mailbox) {
1418 if (!adr->group && adr->next && adr->next->mailbox) {
1428 /* arbitrary number of elements to grow the array by */
1433 /* need to write the list in reverse because they are stored in reverse order
1434 * when parsed to speed up threading
1436 void mutt_write_references (LIST * r, FILE * f)
1439 int refcnt = 0, refmax = 0;
1441 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1442 if (refcnt == refmax)
1443 p_realloc(&ref, refmax += REF_INC);
1447 while (refcnt-- > 0) {
1449 fputs (ref[refcnt]->data, f);
1455 /* Note: all RFC2047 encoding should be done outside of this routine, except
1456 * for the "real name." This will allow this routine to be used more than
1457 * once, if necessary.
1459 * Likewise, all IDN processing should happen outside of this routine.
1461 * mode == 1 => "lite" mode (used for edit_hdrs)
1462 * mode == 0 => normal mode. write full header + MIME headers
1463 * mode == -1 => write just the envelope info (used for postponing messages)
1465 * privacy != 0 => will omit any headers which may identify the user.
1466 * Output generated is suitable for being sent through
1467 * anonymous remailer chains.
1471 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1472 int mode, int privacy)
1474 char buffer[LONG_STRING];
1476 LIST *tmp = env->userhdrs;
1477 int has_agent = 0; /* user defined user-agent header field exists */
1478 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1481 if (!option (OPTNEWSSEND))
1483 if (mode == 0 && !privacy)
1484 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1486 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1488 /* OPTUSEFROM is not consulted here so that we can still write a From:
1489 * field if the user sets it with the `my_hdr' command
1491 if (env->from && !privacy) {
1493 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1494 fprintf (fp, "From: %s\n", buffer);
1499 mutt_write_address_list (env->to, fp, 4, 0);
1503 if (!option (OPTNEWSSEND))
1505 if (EDIT_HEADER("To:"))
1506 fputs ("To:\n", fp);
1510 mutt_write_address_list (env->cc, fp, 4, 0);
1514 if (!option (OPTNEWSSEND))
1516 if (EDIT_HEADER("Cc:"))
1517 fputs ("Cc:\n", fp);
1520 if (mode != 0 || option (OPTWRITEBCC)) {
1521 fputs ("Bcc: ", fp);
1522 mutt_write_address_list (env->bcc, fp, 5, 0);
1527 if (!option (OPTNEWSSEND))
1529 if (EDIT_HEADER("Bcc:"))
1530 fputs ("Bcc:\n", fp);
1533 if (env->newsgroups)
1534 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1535 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1536 fputs ("Newsgroups:\n", fp);
1538 if (env->followup_to)
1539 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1540 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1541 fputs ("Followup-To:\n", fp);
1543 if (env->x_comment_to)
1544 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1545 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1546 EDIT_HEADER("X-Comment-To:"))
1547 fputs ("X-Comment-To:\n", fp);
1551 fprintf (fp, "Subject: %s\n", env->subject);
1552 else if (mode == 1 && EDIT_HEADER("Subject:"))
1553 fputs ("Subject:\n", fp);
1555 /* save message id if the user has set it */
1556 if (env->message_id && !privacy)
1557 fprintf (fp, "Message-ID: %s\n", env->message_id);
1559 if (env->reply_to) {
1560 fputs ("Reply-To: ", fp);
1561 mutt_write_address_list (env->reply_to, fp, 10, 0);
1563 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1564 fputs ("Reply-To:\n", fp);
1566 if (env->mail_followup_to)
1568 if (!option (OPTNEWSSEND))
1571 fputs ("Mail-Followup-To: ", fp);
1572 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1576 if (env->references) {
1577 fputs ("References:", fp);
1578 mutt_write_references (env->references, fp);
1582 /* Add the MIME headers */
1583 fputs ("MIME-Version: 1.0\n", fp);
1584 mutt_write_mime_header (attach, fp);
1587 if (env->in_reply_to) {
1588 fputs ("In-Reply-To:", fp);
1589 mutt_write_references (env->in_reply_to, fp);
1595 /* Add any user defined headers */
1596 for (; tmp; tmp = tmp->next) {
1597 if ((p = strchr (tmp->data, ':'))) {
1598 p = vskipspaces(p + 1);
1600 continue; /* don't emit empty fields. */
1602 /* check to see if the user has overridden the user-agent field */
1603 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1609 fputs (tmp->data, fp);
1614 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1617 if (OperatingSystem != NULL) {
1618 os = OperatingSystem;
1621 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1623 /* Add a vanity header */
1624 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1627 list_del (&hdrs, (list_del_t*)xmemfree);
1629 return (ferror (fp) == 0 ? 0 : -1);
1632 static void encode_headers (LIST * h)
1638 for (; h; h = h->next) {
1639 if (!(p = strchr (h->data, ':')))
1643 p = vskipspaces(p + 1);
1649 rfc2047_encode_string (&tmp);
1650 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1652 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1658 const char *mutt_fqdn (short may_hide_host)
1662 if (Fqdn && Fqdn[0] != '@') {
1665 if (may_hide_host && option (OPTHIDDENHOST)) {
1666 if ((p = strchr (Fqdn, '.')))
1669 /* sanity check: don't hide the host if
1670 * the fqdn is something like detebe.org.
1673 if (!p || !(q = strchr (p, '.')))
1681 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1682 static char mutt_normalized_char(char c)
1684 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1687 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1689 #define APPEND_FMT(fmt, arg) \
1691 int snlen = snprintf(buf, len, fmt, arg); \
1696 #define APPEND_BYTE(c) \
1713 APPEND_BYTE(mutt_normalized_char(c));
1721 APPEND_FMT("%02d", tm->tm_mday);
1724 APPEND_FMT("%02d", tm->tm_hour);
1727 APPEND_FMT("%02d", tm->tm_mon + 1);
1730 APPEND_FMT("%02d", tm->tm_min);
1733 APPEND_FMT("%lo", (unsigned long)now);
1736 APPEND_FMT("%u", (unsigned int)getpid());
1739 APPEND_FMT("%c", MsgIdPfx);
1740 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1743 APPEND_FMT("%u", (unsigned int)rand());
1746 APPEND_FMT("%x", (unsigned int)rand());
1749 APPEND_FMT("%02d", tm->tm_sec);
1752 APPEND_FMT("%u", (unsigned int) now);
1755 APPEND_FMT("%x", (unsigned int) now);
1757 case 'Y': /* this will break in the year 10000 ;-) */
1758 APPEND_FMT("%04d", tm->tm_year + 1900);
1763 default: /* invalid formats are replaced by '.' */
1765 m_strncat(buf, len, ".", 1);
1775 char *mutt_gen_msgid (void)
1777 char buf[SHORT_STRING];
1778 char localpart[SHORT_STRING];
1779 unsigned int localpart_length;
1782 if (!(fqdn = mutt_fqdn (0)))
1783 fqdn = NONULL (Hostname);
1785 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1787 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1789 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1790 return (m_strdup(buf));
1793 static RETSIGTYPE alarm_handler (int sig)
1798 /* invoke sendmail in a subshell
1799 path (in) path to program to execute
1800 args (in) arguments to pass to program
1801 msg (in) temp file containing message to send
1802 tempfile (out) if sendmail is put in the background, this points
1803 to the temporary file containing the stdout of the
1806 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1812 mutt_block_signals_system ();
1815 /* we also don't want to be stopped right now */
1816 sigaddset (&set, SIGTSTP);
1817 sigprocmask (SIG_BLOCK, &set, NULL);
1819 if (SendmailWait >= 0) {
1820 char tmp[_POSIX_PATH_MAX];
1823 *tempfile = m_strdup(tmp);
1826 if ((pid = fork ()) == 0) {
1827 struct sigaction act, oldalrm;
1829 /* save parent's ID before setsid() */
1832 /* we want the delivery to continue even after the main process dies,
1833 * so we put ourselves into another session right away
1837 /* next we close all open files */
1838 #if defined(OPEN_MAX)
1839 for (fd = 0; fd < OPEN_MAX; fd++)
1841 #elif defined(_POSIX_OPEN_MAX)
1842 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1850 /* now the second fork() */
1851 if ((pid = fork ()) == 0) {
1852 /* "msg" will be opened as stdin */
1853 if (open (msg, O_RDONLY, 0) < 0) {
1859 if (SendmailWait >= 0) {
1860 /* *tempfile will be opened as stdout */
1861 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1864 /* redirect stderr to *tempfile too */
1869 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1871 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1878 else if (pid == -1) {
1884 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1885 * SendmailWait = 0: wait forever
1886 * SendmailWait < 0: don't wait
1888 if (SendmailWait > 0) {
1890 act.sa_handler = alarm_handler;
1892 /* need to make sure waitpid() is interrupted on SIGALRM */
1893 act.sa_flags = SA_INTERRUPT;
1897 sigemptyset (&act.sa_mask);
1898 sigaction (SIGALRM, &act, &oldalrm);
1899 alarm (SendmailWait);
1901 else if (SendmailWait < 0)
1902 _exit (0xff & EX_OK);
1904 if (waitpid (pid, &st, 0) > 0) {
1905 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1906 if (SendmailWait && st == (0xff & EX_OK)) {
1907 unlink (*tempfile); /* no longer needed */
1912 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1913 if (SendmailWait > 0) {
1919 /* reset alarm; not really needed, but... */
1921 sigaction (SIGALRM, &oldalrm, NULL);
1923 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1924 /* the parent is already dead */
1932 sigprocmask (SIG_UNBLOCK, &set, NULL);
1934 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1935 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1937 st = S_ERR; /* error */
1939 mutt_unblock_signals_system (1);
1944 static const char **
1945 add_args(const char **args, size_t *argslen, size_t *argsmax, ADDRESS * addr)
1947 for (; addr; addr = addr->next) {
1948 /* weed out group mailboxes, since those are for display only */
1949 if (addr->mailbox && !addr->group) {
1950 if (*argslen == *argsmax)
1951 p_realloc(&args, *argsmax += 5);
1952 args[(*argslen)++] = addr->mailbox;
1958 static const char **
1959 add_option(const char **args, size_t *argslen, size_t *argsmax, const char *s)
1961 if (*argslen == *argsmax) {
1962 p_realloc(&args, *argsmax += 5);
1964 args[(*argslen)++] = s;
1968 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
1969 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
1970 const char *msg, /* file containing message */
1972 { /* message contains 8bit chars */
1973 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1974 const char **args = NULL;
1975 size_t argslen = 0, argsmax = 0;
1979 if (option (OPTNEWSSEND)) {
1980 char cmd[LONG_STRING];
1982 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1985 i = nntp_post (msg);
1994 s = m_strdup(Sendmail);
1998 while ((ps = strtok (ps, " "))) {
1999 if (argslen == argsmax)
2000 p_realloc(&args, argsmax += 5);
2003 args[argslen++] = ps;
2005 path = m_strdup(ps);
2006 ps = strrchr (ps, '/');
2011 args[argslen++] = ps;
2018 if (!option (OPTNEWSSEND)) {
2020 if (eightbit && option (OPTUSE8BITMIME))
2021 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
2023 if (option (OPTENVFROM)) {
2027 else if (from && !from->next)
2030 args = add_option (args, &argslen, &argsmax, "-f");
2031 args = add_args (args, &argslen, &argsmax, f);
2035 args = add_option (args, &argslen, &argsmax, "-N");
2036 args = add_option (args, &argslen, &argsmax, DsnNotify);
2039 args = add_option (args, &argslen, &argsmax, "-R");
2040 args = add_option (args, &argslen, &argsmax, DsnReturn);
2042 args = add_option (args, &argslen, &argsmax, "--");
2043 args = add_args (args, &argslen, &argsmax, to);
2044 args = add_args (args, &argslen, &argsmax, cc);
2045 args = add_args (args, &argslen, &argsmax, bcc);
2050 if (argslen == argsmax)
2051 p_realloc(&args, ++argsmax);
2053 args[argslen++] = NULL;
2055 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2057 const char *e = mutt_strsysexit (i);
2059 e = mutt_strsysexit (i);
2060 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2065 if (stat (childout, &st) == 0 && st.st_size > 0)
2066 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2074 p_delete(&childout);
2079 if (i == (EX_OK & 0xff))
2081 else if (i == S_BKG)
2088 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2089 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2090 const char *msg, /* file containing message */
2092 { /* message contains 8bit chars */
2095 if (!option (OPTNEWSSEND))
2098 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2101 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2104 /* appends string 'b' to string 'a', and returns the pointer to the new
2106 char *mutt_append_string (char *a, const char *b)
2108 size_t la = m_strlen(a);
2110 p_realloc(&a, la + m_strlen(b) + 1);
2111 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2115 /* returns 1 if char `c' needs to be quoted to protect from shell
2116 interpretation when executing commands in a subshell */
2117 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2119 /* returns 1 if string `s' contains characters which could cause problems
2120 when used on a command line to execute a command */
2121 int mutt_needs_quote (const char *s)
2124 if (INVALID_CHAR (*s))
2131 /* Quote a string to prevent shell escapes when this string is used on the
2132 command line to send mail. */
2133 char *mutt_quote_string (const char *s)
2138 rlen = m_strlen(s) + 3;
2139 pr = r = p_new(char, rlen);
2142 if (INVALID_CHAR (*s)) {
2145 p_realloc(&r, ++rlen);
2156 /* For postponing (!final) do the necessary encodings only */
2157 void mutt_prepare_envelope (ENVELOPE * env, int final)
2159 char buffer[LONG_STRING];
2162 if (env->bcc && !(env->to || env->cc)) {
2163 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2164 * recipients if there is no To: or Cc: field, so attempt to suppress
2165 * it by using an empty To: field.
2167 env->to = rfc822_new_address ();
2169 env->to->next = rfc822_new_address ();
2172 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2175 env->to->mailbox = m_strdup(buffer);
2178 mutt_set_followup_to (env);
2180 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2181 env->message_id = mutt_gen_msgid ();
2184 /* Take care of 8-bit => 7-bit conversion. */
2185 rfc2047_encode_adrlist (env->to, "To");
2186 rfc2047_encode_adrlist (env->cc, "Cc");
2187 rfc2047_encode_adrlist (env->bcc, "Bcc");
2188 rfc2047_encode_adrlist (env->from, "From");
2189 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2190 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2194 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2197 rfc2047_encode_string (&env->subject);
2199 encode_headers (env->userhdrs);
2202 void mutt_unprepare_envelope (ENVELOPE * env)
2206 for (item = env->userhdrs; item; item = item->next)
2207 rfc2047_decode (&item->data);
2209 rfc822_free_address (&env->mail_followup_to);
2211 /* back conversions */
2212 rfc2047_decode_adrlist (env->to);
2213 rfc2047_decode_adrlist (env->cc);
2214 rfc2047_decode_adrlist (env->bcc);
2215 rfc2047_decode_adrlist (env->from);
2216 rfc2047_decode_adrlist (env->reply_to);
2217 rfc2047_decode (&env->subject);
2220 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2221 const char *resent_from, ADDRESS * env_from)
2225 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2226 MESSAGE *msg = NULL;
2229 /* Try to bounce each message out, aborting if we get any failures. */
2230 for (i = 0; i < Context->msgcount; i++)
2231 if (Context->hdrs[i]->tagged)
2233 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2238 /* If we failed to open a message, return with error */
2239 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2245 mutt_mktemp (tempfile);
2246 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2247 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2249 if (!option (OPTBOUNCEDELIVERED))
2250 ch_flags |= CH_WEED_DELIVERED;
2252 fseeko (fp, h->offset, 0);
2253 fprintf (f, "Resent-From: %s", resent_from);
2254 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2255 if (MsgIdFormat && *MsgIdFormat)
2256 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2257 fputs ("Resent-To: ", f);
2258 mutt_write_address_list (to, f, 11, 0);
2259 mutt_copy_header (fp, h, f, ch_flags, NULL);
2261 mutt_copy_bytes (fp, f, h->content->length);
2264 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2265 h->content->encoding == ENC8BIT);
2269 mx_close_message (&msg);
2274 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2277 const char *fqdn = mutt_fqdn (1);
2278 char resent_from[STRING];
2282 resent_from[0] = '\0';
2283 from = mutt_default_from ();
2286 rfc822_qualify (from, fqdn);
2288 rfc2047_encode_adrlist (from, "Resent-From");
2289 if (mutt_addrlist_to_idna (from, &err)) {
2290 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2293 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2296 unset_option (OPTNEWSSEND);
2299 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2301 rfc822_free_address (&from);
2307 /* given a list of addresses, return a list of unique addresses */
2308 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2310 ADDRESS *top = addr;
2311 ADDRESS **last = ⊤
2316 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2317 if (tmp->mailbox && addr->mailbox &&
2318 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2325 debug_print (2, ("Removing %s\n", addr->mailbox));
2330 rfc822_free_address (&addr);
2343 static void set_noconv_flags (BODY * b, short flag)
2345 for (; b; b = b->next) {
2346 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2347 set_noconv_flags (b->parts, flag);
2348 else if (b->type == TYPETEXT && b->noconv) {
2350 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2352 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2357 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2358 int post, char *fcc)
2362 char tempfile[_POSIX_PATH_MAX];
2363 FILE *tempfp = NULL;
2367 set_noconv_flags (hdr->content, 1);
2369 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2370 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2374 /* We need to add a Content-Length field to avoid problems where a line in
2375 * the message body begins with "From "
2377 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2378 mutt_mktemp (tempfile);
2379 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2380 mutt_perror (tempfile);
2381 mx_close_mailbox (&f, NULL);
2386 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2387 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2388 mx_close_mailbox (&f, NULL);
2392 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2393 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2395 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2398 /* (postponment) if this was a reply of some sort, <msgid> contians the
2399 * Message-ID: of message replied to. Save it using a special X-Mutt-
2400 * header so it can be picked up if the message is recalled at a later
2401 * point in time. This will allow the message to be marked as replied if
2402 * the same mailbox is still open.
2405 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2407 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2408 * it can be picked up when the message is recalled
2411 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2412 fprintf (msg->fp, "Status: RO\n");
2416 /* (postponment) if the mail is to be signed or encrypted, save this info */
2417 if ((WithCrypto & APPLICATION_PGP)
2418 && post && (hdr->security & APPLICATION_PGP)) {
2419 fputs ("X-Mutt-PGP: ", msg->fp);
2420 if (hdr->security & ENCRYPT)
2421 fputc ('E', msg->fp);
2422 if (hdr->security & SIGN) {
2423 fputc ('S', msg->fp);
2424 if (PgpSignAs && *PgpSignAs)
2425 fprintf (msg->fp, "<%s>", PgpSignAs);
2427 if (hdr->security & INLINE)
2428 fputc ('I', msg->fp);
2429 fputc ('\n', msg->fp);
2432 /* (postponment) if the mail is to be signed or encrypted, save this info */
2433 if ((WithCrypto & APPLICATION_SMIME)
2434 && post && (hdr->security & APPLICATION_SMIME)) {
2435 fputs ("X-Mutt-SMIME: ", msg->fp);
2436 if (hdr->security & ENCRYPT) {
2437 fputc ('E', msg->fp);
2438 if (SmimeCryptAlg && *SmimeCryptAlg)
2439 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2441 if (hdr->security & SIGN) {
2442 fputc ('S', msg->fp);
2443 if (SmimeDefaultKey && *SmimeDefaultKey)
2444 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2446 if (hdr->security & INLINE)
2447 fputc ('I', msg->fp);
2448 fputc ('\n', msg->fp);
2452 /* (postponement) if the mail is to be sent through a mixmaster
2453 * chain, save that information
2456 if (post && hdr->chain && hdr->chain) {
2459 fputs ("X-Mutt-Mix:", msg->fp);
2460 for (p = hdr->chain; p; p = p->next)
2461 fprintf (msg->fp, " %s", (char *) p->data);
2463 fputc ('\n', msg->fp);
2468 char sasha[LONG_STRING];
2471 mutt_write_mime_body (hdr->content, tempfp);
2473 /* make sure the last line ends with a newline. Emacs doesn't ensure
2474 * this will happen, and it can cause problems parsing the mailbox
2477 fseeko (tempfp, -1, 2);
2478 if (fgetc (tempfp) != '\n') {
2479 fseeko (tempfp, 0, 2);
2480 fputc ('\n', tempfp);
2484 if (ferror (tempfp)) {
2485 debug_print (1, ("%s: write failed.\n", tempfile));
2488 mx_commit_message (msg, &f); /* XXX - really? */
2489 mx_close_message (&msg);
2490 mx_close_mailbox (&f, NULL);
2494 /* count the number of lines */
2496 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2498 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2499 fprintf (msg->fp, "Lines: %d\n\n", lines);
2501 /* copy the body and clean up */
2503 r = mutt_copy_stream (tempfp, msg->fp);
2504 if (fclose (tempfp) != 0)
2506 /* if there was an error, leave the temp version */
2511 fputc ('\n', msg->fp); /* finish off the header */
2512 r = mutt_write_mime_body (hdr->content, msg->fp);
2515 if (mx_commit_message (msg, &f) != 0)
2517 mx_close_message (&msg);
2518 mx_close_mailbox (&f, NULL);
2521 set_noconv_flags (hdr->content, 0);