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.
10 #include <lib-lib/lib-lib.h>
14 #include <lib-lua/lib-lua.h>
15 #include <lib-sys/exit.h>
16 #include <lib-sys/mutt_signal.h>
17 #include <lib-mime/mime.h>
18 #include <lib-ui/curses.h>
19 #include <lib-mx/mx.h>
21 #include <lib-crypt/crypt.h>
25 #include "recvattach.h"
29 #include "mutt_idna.h"
30 #include "mutt_libesmtp.h"
33 #include <nntp/nntp.h>
36 #ifdef HAVE_SYSEXITS_H
38 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
42 static void transform_to_7bit (BODY * a, FILE * fpin);
44 static void encode_quoted (fgetconv_t * fc, FILE * fout, int istext)
47 char line[77], savechar;
49 while ((c = fgetconv (fc)) != EOF) {
50 /* Wrap the line if needed. */
51 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
52 /* If the last character is "quoted", then be sure to move all three
53 * characters to the next line. Otherwise, just move the last
56 if (line[linelen - 3] == '=') {
57 line[linelen - 3] = 0;
62 line[1] = line[linelen - 2];
63 line[2] = line[linelen - 1];
67 savechar = line[linelen - 1];
68 line[linelen - 1] = '=';
77 /* Escape lines that begin with/only contain "the message separator". */
78 if (linelen == 4 && !m_strncmp("From", line, 4)) {
79 m_strcpy(line, sizeof(line), "=46rom");
82 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
83 m_strcpy(line, sizeof(line), "=66rom");
86 else if (linelen == 1 && line[0] == '.') {
87 m_strcpy(line, sizeof(line), "=2E");
92 if (c == '\n' && istext) {
93 /* Check to make sure there is no trailing space on this line. */
95 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
97 sprintf (line + linelen - 1, "=%2.2X",
98 (unsigned char) line[linelen - 1]);
102 savechar = line[linelen - 1];
104 line[linelen - 1] = '=';
107 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
117 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
118 /* Check to make sure there is enough room for the quoted character.
119 * If not, wrap to the next line.
122 line[linelen++] = '=';
128 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
132 /* Don't worry about wrapping the line here. That will happen during
133 * the next iteration when I'll also know what the next character is.
139 /* Take care of anything left in the buffer */
141 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
142 /* take care of trailing whitespace */
144 sprintf (line + linelen - 1, "=%2.2X",
145 (unsigned char) line[linelen - 1]);
147 savechar = line[linelen - 1];
148 line[linelen - 1] = '=';
152 sprintf (line, "=%2.2X", (unsigned char) savechar);
161 static char b64_buffer[3];
162 static short b64_num;
163 static short b64_linelen;
165 static void b64_flush (FILE * fout)
172 if (b64_linelen >= 72) {
177 for (i = b64_num; i < 3; i++)
178 b64_buffer[i] = '\0';
180 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
183 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
188 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
192 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
197 while (b64_linelen % 4) {
206 static void b64_putc (char c, FILE * fout)
211 b64_buffer[b64_num++] = c;
215 static void encode_base64 (fgetconv_t * fc, FILE * fout, int istext)
219 b64_num = b64_linelen = 0;
221 while ((ch = fgetconv (fc)) != EOF) {
222 if (istext && ch == '\n' && ch1 != '\r')
223 b64_putc ('\r', fout);
231 static void encode_8bit (fgetconv_t * fc, FILE * fout,
232 int istext __attribute__ ((unused)))
236 while ((ch = fgetconv (fc)) != EOF)
241 int mutt_write_mime_header (BODY * a, FILE * f)
250 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
255 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
257 for (p = a->parameter; p; p = p->next) {
266 tmp = m_strdup(p->value);
267 encode = rfc2231_encode_string (&tmp);
268 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
270 /* Dirty hack to make messages readable by Outlook Express
271 * for the Mac: force quotes around the boundary parameter
272 * even when they aren't needed.
275 if (!ascii_strcasecmp (p->attribute, "boundary")
276 && !strcmp (buffer, tmp))
277 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
281 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
283 if (len + tmplen + 2 > 76) {
292 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
300 fprintf (f, "Content-Description: %s\n", a->description);
302 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
303 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
306 if (!(fn = a->d_filename))
312 /* Strip off the leading path... */
313 if ((t = strrchr (fn, '/')))
320 encode = rfc2231_encode_string (&tmp);
321 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
323 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
329 if (a->encoding != ENC7BIT)
330 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
332 /* Do NOT add the terminator here!!! */
333 return (ferror (f) ? -1 : 0);
336 int mutt_write_mime_body (BODY * a, FILE * f)
339 char boundary[STRING];
340 char send_charset[STRING];
345 if (a->type == TYPEMULTIPART) {
346 /* First, find the boundary to use */
347 if (!(p = parameter_getval(a->parameter, "boundary"))) {
348 mutt_error _("No boundary parameter found! [report this error]");
352 m_strcpy(boundary, sizeof(boundary), p);
354 for (t = a->parts; t; t = t->next) {
355 fprintf (f, "\n--%s\n", boundary);
356 if (mutt_write_mime_header (t, f) == -1)
359 if (mutt_write_mime_body (t, f) == -1)
362 fprintf (f, "\n--%s--\n", boundary);
363 return (ferror (f) ? -1 : 0);
366 /* This is pretty gross, but it's the best solution for now... */
367 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
368 fputs ("Version: 1\n", f);
372 if ((fpin = fopen (a->filename, "r")) == NULL) {
373 mutt_error (_("%s no longer exists!"), a->filename);
377 if (a->type == TYPETEXT && (!a->noconv))
378 fc = fgetconv_open (fpin, a->file_charset,
379 mutt_get_body_charset (send_charset,
380 sizeof (send_charset), a), 0);
382 fc = fgetconv_open (fpin, 0, 0, 0);
384 #define write_as_text_part(a) (mutt_is_text_part(a) || mutt_is_application_pgp(a))
385 if (a->encoding == ENCQUOTEDPRINTABLE)
386 encode_quoted (fc, f, write_as_text_part (a));
387 else if (a->encoding == ENCBASE64)
388 encode_base64 (fc, f, write_as_text_part (a));
389 else if (a->type == TYPETEXT && (!a->noconv))
390 encode_8bit (fc, f, write_as_text_part (a));
392 mutt_copy_stream (fpin, f);
393 #undef write_as_text_part
395 fgetconv_close (&fc);
398 return (ferror (f) ? -1 : 0);
410 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
414 int whitespace = s->whitespace;
416 int linelen = s->linelen;
417 int was_cr = s->was_cr;
419 if (!d) { /* This signals EOF */
422 if (linelen > info->linemax)
423 info->linemax = linelen;
428 for (; dlen; d++, dlen--) {
441 if (linelen > info->linemax)
442 info->linemax = linelen;
457 if (linelen > info->linemax)
458 info->linemax = linelen;
463 else if (ch == '\r') {
471 else if (ch == '\t' || ch == '\f') {
475 else if (ch < 32 || ch == 127)
479 if ((ch == 'F') || (ch == 'f'))
489 if (linelen == 2 && ch != 'r')
491 else if (linelen == 3 && ch != 'o')
493 else if (linelen == 4) {
506 if (ch != ' ' && ch != '\t')
511 s->whitespace = whitespace;
513 s->linelen = linelen;
519 * Find the best charset conversion of the file from fromcode into one
520 * of the tocodes. If successful, set *tocode and CONTENT *info and
521 * return the number of characters converted inexactly. If no
522 * conversion was possible, return -1.
524 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
525 * which would otherwise prevent us from knowing the number of inexact
526 * conversions. Where the candidate target charset is UTF-8 we avoid
527 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
528 * fails with some libraries.
530 * We assume that the output from iconv is never more than 4 times as
531 * long as the input for any pair of charsets we might be interested
534 static ssize_t convert_file_to (FILE * file, const char *fromcode,
535 int ncodes, const char **tocodes,
536 int *tocode, CONTENT * info)
540 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
543 ssize_t ibl, obl, ubl, ubl1, n, ret;
546 CONTENT_STATE *states;
549 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
550 if (cd1 == MUTT_ICONV_ERROR)
553 cd = p_new(iconv_t, ncodes);
554 score = p_new(ssize_t, ncodes);
555 states = p_new(CONTENT_STATE, ncodes);
556 infos = p_new(CONTENT, ncodes);
558 for (i = 0; i < ncodes; i++)
559 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
560 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
562 /* Special case for conversion to UTF-8 */
563 cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
569 /* Try to fill input buffer */
570 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
573 /* Convert to UTF-8 */
575 ob = bufu, obl = sizeof (bufu);
576 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
577 if (n == -1 && ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
583 /* Convert from UTF-8 */
584 for (i = 0; i < ncodes; i++)
585 if (cd[i] != MUTT_ICONV_ERROR && score[i] != -1) {
586 ub = bufu, ubl = ubl1;
587 ob = bufo, obl = sizeof (bufo);
588 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
594 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
597 else if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1)
598 /* Special case for conversion to UTF-8 */
599 update_content_info (&infos[i], &states[i], bufu, ubl1);
602 /* Save unused input */
603 memmove (bufi, ib, ibl);
604 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
611 /* Find best score */
613 for (i = 0; i < ncodes; i++) {
614 if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1) {
615 /* Special case for conversion to UTF-8 */
620 else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
622 else if (ret == -1 || score[i] < ret) {
630 memcpy (info, &infos[*tocode], sizeof (CONTENT));
631 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
635 for (i = 0; i < ncodes; i++)
636 if (cd[i] != MUTT_ICONV_ERROR)
648 #endif /* !HAVE_ICONV */
652 * Find the first of the fromcodes that gives a valid conversion and
653 * the best charset conversion of the file into one of the tocodes. If
654 * successful, set *fromcode and *tocode to dynamically allocated
655 * strings, set CONTENT *info, and return the number of characters
656 * converted inexactly. If no conversion was possible, return -1.
658 * Both fromcodes and tocodes may be colon-separated lists of charsets.
659 * However, if fromcode is zero then fromcodes is assumed to be the
660 * name of a single charset even if it contains a colon.
662 static ssize_t convert_file_from_to (FILE * file,
663 const char *fromcodes,
664 const char *tocodes, char **fromcode,
665 char **tocode, CONTENT * info)
673 /* Count the tocodes */
675 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
676 if ((c1 = strchr (c, ':')) == c)
682 tcode = p_new(char *, ncodes);
683 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
684 if ((c1 = strchr (c, ':')) == c)
686 tcode[i] = m_substrdup(c, c1);
691 /* Try each fromcode in turn */
692 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
693 if ((c1 = strchr (c, ':')) == c)
695 fcode = m_substrdup(c, c1);
697 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
709 /* There is only one fromcode */
710 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
719 for (i = 0; i < ncodes; i++)
728 * Analyze the contents of a file to determine which MIME encoding to use.
729 * Also set the body charset, sometimes, or not.
731 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
736 char *fromcode = NULL;
747 if (stat (fname, &sb) == -1) {
748 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
752 if (!S_ISREG (sb.st_mode)) {
753 mutt_error (_("%s isn't a regular file."), fname);
757 if ((fp = fopen (fname, "r")) == NULL) {
761 info = p_new(CONTENT, 1);
764 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
765 const char *chs = parameter_getval(b->parameter, "charset");
766 char *fchs = b->use_disp && !m_strisempty(MCharset.file_charset)
767 ? FileCharset : MCharset.charset;
768 if (MCharset.charset && (chs || MCharset.send_charset) &&
769 convert_file_from_to (fp, fchs, chs ? chs : MCharset.send_charset,
770 &fromcode, &tocode, info) != -1) {
772 charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
773 parameter_setval(&b->parameter, "charset", chsbuf);
775 b->file_charset = fromcode;
783 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
784 update_content_info (info, &state, buffer, r);
785 update_content_info (info, &state, 0, 0);
789 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
790 parameter_setval(&b->parameter, "charset",
791 (!info->hibin ? "us-ascii"
792 : MCharset.charset && !charset_is_us_ascii(MCharset.charset)
793 ? MCharset.charset : "unknown-8bit"));
798 /* Given a file with path ``s'', see if there is a registered MIME type.
799 * returns the major MIME type, and copies the subtype to ``d''. First look
800 * for ~/.mime.types, then look in a system mime.types if we can find one.
801 * The longest match is used so that we can match `ps.gz' when `gz' also
805 int mutt_lookup_mime_type (BODY * att, const char *path)
809 char buf[LONG_STRING];
810 char subtype[STRING], xtype[STRING];
812 int szf, sze, cur_sze;
820 szf = m_strlen(path);
822 for (count = 0; count < 4; count++) {
824 * can't use strtok() because we use it in an inner loop below, so use
825 * a switch statement here instead.
829 snprintf(buf, sizeof (buf), "%s/.mime.types", NONULL(MCore.homedir));
832 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
835 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
838 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
841 goto bye; /* shouldn't happen */
844 if ((f = fopen (buf, "r")) != NULL) {
845 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
846 /* weed out any comments */
847 if ((p = strchr (buf, '#')))
850 /* remove any leading space. */
851 ct = vskipspaces(buf);
853 /* position on the next field in this line */
854 if ((p = strpbrk (ct, " \t")) == NULL)
859 /* cycle through the file extensions */
860 while ((p = strtok (p, " \t\n"))) {
862 if ((sze > cur_sze) && (szf >= sze) &&
863 (m_strcasecmp(path + szf - sze, p) == 0
864 || ascii_strcasecmp (path + szf - sze, p) == 0)
865 && (szf == sze || path[szf - sze - 1] == '.'))
867 /* get the content-type */
869 if ((p = strchr (ct, '/')) == NULL) {
870 /* malformed line, just skip it. */
875 for (q = p; *q && !ISSPACE (*q); q++);
877 m_strncpy(subtype, sizeof(subtype), p, q - p);
879 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
880 m_strcpy(xtype, sizeof(xtype), ct);
893 if (type != TYPEOTHER || *xtype != '\0') {
895 m_strreplace(&att->subtype, subtype);
896 m_strreplace(&att->xtype, xtype);
902 void mutt_message_to_7bit (BODY * a, FILE * fp)
904 char temp[_POSIX_PATH_MAX];
910 if (!a->filename && fp)
912 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
913 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
918 if (stat (a->filename, &sb) == -1) {
919 mutt_perror ("stat");
922 a->length = sb.st_size;
925 fpout = m_tempfile(temp, sizeof(temp), NONULL(MCore.tmpdir), NULL);
927 mutt_error(_("Could not create temporary file"));
931 fseeko (fpin, a->offset, 0);
932 a->parts = mutt_parse_messageRFC822 (fpin, a);
934 transform_to_7bit (a->parts, fpin);
936 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
937 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
939 fputs ("MIME-Version: 1.0\n", fpout);
940 mutt_write_mime_header (a->parts, fpout);
942 mutt_write_mime_body (a->parts, fpout);
954 a->encoding = ENC7BIT;
955 a->d_filename = a->filename;
956 if (a->filename && a->unlink)
957 unlink (a->filename);
958 a->filename = m_strdup(temp);
960 if (stat (a->filename, &sb) == -1) {
961 mutt_perror ("stat");
964 a->length = sb.st_size;
965 body_list_wipe(&a->parts);
966 a->hdr->content = NULL;
969 static void transform_to_7bit (BODY * a, FILE * fpin)
971 char buff[_POSIX_PATH_MAX];
976 for (; a; a = a->next) {
977 if (a->type == TYPEMULTIPART) {
978 if (a->encoding != ENC7BIT)
979 a->encoding = ENC7BIT;
981 transform_to_7bit (a->parts, fpin);
983 else if (mutt_is_message_type(a)) {
984 mutt_message_to_7bit (a, fpin);
988 a->force_charset = 1;
990 s.fpout = m_tempfile(buff, sizeof(buff), NONULL(MCore.tmpdir), NULL);
992 mutt_error(_("Could not create temporary file"));
996 mutt_decode_attachment (a, &s);
998 a->d_filename = a->filename;
999 a->filename = m_strdup(buff);
1001 if (stat (a->filename, &sb) == -1) {
1002 mutt_perror ("stat");
1005 a->length = sb.st_size;
1007 mutt_update_encoding (a);
1008 if (a->encoding == ENC8BIT)
1009 a->encoding = ENCQUOTEDPRINTABLE;
1010 else if (a->encoding == ENCBINARY)
1011 a->encoding = ENCBASE64;
1016 /* determine which Content-Transfer-Encoding to use */
1017 static void mutt_set_encoding (BODY * b, CONTENT * info)
1019 char send_charset[STRING];
1021 if (b->type == TYPETEXT) {
1023 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1024 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1025 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1026 b->encoding = ENCQUOTEDPRINTABLE;
1027 else if (info->hibin)
1028 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1030 b->encoding = ENC7BIT;
1032 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1033 if (info->lobin || info->hibin) {
1034 if (option (OPTALLOW8BIT) && !info->lobin)
1035 b->encoding = ENC8BIT;
1037 mutt_message_to_7bit (b, NULL);
1040 b->encoding = ENC7BIT;
1042 else if (b->type == TYPEAPPLICATION
1043 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1044 b->encoding = ENC7BIT;
1047 /* Determine which encoding is smaller */
1048 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1049 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1050 b->encoding = ENCBASE64;
1052 b->encoding = ENCQUOTEDPRINTABLE;
1056 void mutt_stamp_attachment (BODY * a)
1058 a->stamp = time (NULL);
1061 /* Get a body's character set */
1063 char *mutt_get_body_charset(char *d, ssize_t dlen, BODY * b)
1067 if (b && b->type != TYPETEXT)
1070 p = b ? parameter_getval(b->parameter, "charset") : NULL;
1071 charset_canonicalize(d, dlen, p);
1076 /* Assumes called from send mode where BODY->filename points to actual file */
1077 void mutt_update_encoding (BODY * a)
1080 char chsbuff[STRING];
1082 /* override noconv when it's us-ascii */
1083 if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1086 if (!a->force_charset && !a->noconv)
1087 parameter_delval(&a->parameter, "charset");
1089 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1092 mutt_set_encoding (a, info);
1093 mutt_stamp_attachment (a);
1095 p_delete(&a->content);
1100 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1102 char buffer[LONG_STRING];
1105 int cmflags, chflags;
1106 int pgp = hdr->security;
1108 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1109 (hdr->security & ENCRYPT)) {
1112 fp = m_tempfile(buffer, sizeof(buffer), NONULL(MCore.tmpdir), NULL);
1117 body->type = TYPEMESSAGE;
1118 body->subtype = m_strdup("rfc822");
1119 body->filename = m_strdup(buffer);
1122 body->disposition = DISPINLINE;
1125 mutt_parse_mime_message (ctx, hdr);
1130 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1131 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1132 chflags |= CH_MIME | CH_TXTPLAIN;
1133 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1134 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1136 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1137 if (mutt_is_multipart_encrypted (hdr->content)) {
1138 chflags |= CH_MIME | CH_NONEWLINE;
1139 cmflags = M_CM_DECODE_PGP;
1142 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1143 chflags |= CH_MIME | CH_TXTPLAIN;
1144 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1147 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1148 chflags |= CH_MIME | CH_TXTPLAIN;
1149 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1150 pgp &= ~SMIMEENCRYPT;
1154 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1159 body->hdr = header_new();
1160 body->hdr->offset = 0;
1161 /* we don't need the user headers here */
1162 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1163 body->hdr->security = pgp;
1164 mutt_update_encoding (body);
1165 body->parts = body->hdr->content;
1172 BODY *mutt_make_file_attach (const char *path)
1178 att->filename = m_strdup(path);
1180 /* Attempt to determine the appropriate content-type based on the filename
1183 mutt_lookup_mime_type (att, path);
1185 if ((info = mutt_get_content_info (path, att)) == NULL) {
1186 body_list_wipe(&att);
1190 if (!att->subtype) {
1191 if (info->lobin == 0
1192 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1194 * Statistically speaking, there should be more than 10% "lobin"
1195 * chars if this is really a binary file...
1197 att->type = TYPETEXT;
1198 att->subtype = m_strdup("plain");
1200 att->type = TYPEAPPLICATION;
1201 att->subtype = m_strdup("octet-stream");
1205 mutt_update_encoding (att);
1209 static int get_toplevel_encoding (BODY * a)
1213 for (; a; a = a->next) {
1214 if (a->encoding == ENCBINARY)
1217 if (a->encoding == ENC8BIT)
1224 BODY *mutt_make_multipart (BODY * b)
1229 new->type = TYPEMULTIPART;
1230 new->subtype = m_strdup("mixed");
1231 new->encoding = get_toplevel_encoding (b);
1232 parameter_set_boundary(&new->parameter);
1234 new->disposition = DISPINLINE;
1240 /* remove the multipart body if it exists */
1241 BODY *mutt_remove_multipart (BODY * b)
1254 char *mutt_make_date (char *s, ssize_t len)
1256 time_t t = time (NULL);
1257 struct tm *l = localtime (&t);
1258 time_t tz = mutt_local_tz (t);
1262 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1263 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1264 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1265 (int) tz / 60, (int) abs (tz) % 60);
1269 /* wrapper around mutt_write_address() so we can handle very large
1270 recipient lists without needing a huge temporary buffer in memory */
1272 mutt_write_address_list(address_t *addr, FILE *fp, int linelen, int display)
1277 char buf[LONG_STRING];
1278 int len = rfc822_addrcpy(buf, ssizeof(buf), addr, display);
1281 if (linelen + len > 74) {
1283 linelen = 8; /* tab is usually about 8 spaces... */
1285 if (addr->mailbox) {
1295 if (!addr->group && addr->next && addr->next->mailbox) {
1305 /* need to write the list in reverse because they are stored in reverse order
1306 * when parsed to speed up threading
1308 void mutt_write_references(string_list_t *r, FILE *f)
1310 string_list_t *refs[10];
1313 p_clear(refs, countof(refs));
1314 for (i = 0; i < countof(refs) && r; r = r->next) {
1319 fprintf(f, " %s", refs[i]->data);
1323 static int edit_header(int mode, const char *s)
1326 int slen = m_strlen(s);
1328 if (mode != 1 || option(OPTXMAILTO))
1331 p = skipspaces(EditorHeaders);
1333 if (!ascii_strncasecmp(p, s, slen) && p[slen - 1] == ':')
1335 p = skipspaces(p + slen);
1341 /* Note: all RFC2047 encoding should be done outside of this routine, except
1342 * for the "real name." This will allow this routine to be used more than
1343 * once, if necessary.
1345 * Likewise, all IDN processing should happen outside of this routine.
1347 * mode == 1 => "lite" mode (used for edit_hdrs)
1348 * mode == 0 => normal mode. write full header + MIME headers
1349 * mode == -1 => write just the envelope info (used for postponing messages)
1351 * privacy != 0 => will omit any headers which may identify the user.
1352 * Output generated is suitable for being sent through
1353 * anonymous remailer chains.
1356 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1357 int mode, int privacy)
1359 char buffer[LONG_STRING];
1361 string_list_t *tmp = env->userhdrs;
1362 int has_agent = 0; /* user defined user-agent header field exists */
1365 if (!option (OPTNEWSSEND))
1367 if (mode == 0 && !privacy)
1368 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1370 /* OPTUSEFROM is not consulted here so that we can still write a From:
1371 * field if the user sets it with the `my_hdr' command
1373 if (env->from && !privacy) {
1375 rfc822_addrcat(buffer, sizeof(buffer), env->from, 0);
1376 fprintf (fp, "From: %s\n", buffer);
1381 mutt_write_address_list (env->to, fp, 4, 0);
1385 if (!option (OPTNEWSSEND))
1387 if (edit_header(mode, "To:"))
1388 fputs ("To:\n", fp);
1392 mutt_write_address_list (env->cc, fp, 4, 0);
1396 if (!option (OPTNEWSSEND))
1398 if (edit_header(mode, "Cc:"))
1399 fputs ("Cc:\n", fp);
1402 if (mode != 0 || option (OPTWRITEBCC)) {
1403 fputs ("Bcc: ", fp);
1404 mutt_write_address_list (env->bcc, fp, 5, 0);
1409 if (!option (OPTNEWSSEND))
1411 if (edit_header(mode, "Bcc:"))
1412 fputs ("Bcc:\n", fp);
1415 if (env->newsgroups)
1416 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1417 else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Newsgroups:"))
1418 fputs ("Newsgroups:\n", fp);
1420 if (env->followup_to)
1421 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1422 else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Followup-To:"))
1423 fputs ("Followup-To:\n", fp);
1425 if (env->x_comment_to)
1426 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1427 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1428 edit_header(mode, "X-Comment-To:"))
1429 fputs ("X-Comment-To:\n", fp);
1433 fprintf (fp, "Subject: %s\n", env->subject);
1434 else if (mode == 1 && edit_header(mode, "Subject:"))
1435 fputs ("Subject:\n", fp);
1437 /* save message id if the user has set it */
1438 if (env->message_id && !privacy)
1439 fprintf (fp, "Message-ID: %s\n", env->message_id);
1441 if (env->reply_to) {
1442 fputs ("Reply-To: ", fp);
1443 mutt_write_address_list (env->reply_to, fp, 10, 0);
1445 else if (mode > 0 && edit_header(mode, "Reply-To:"))
1446 fputs ("Reply-To:\n", fp);
1448 if (env->mail_followup_to)
1450 if (!option (OPTNEWSSEND))
1453 fputs ("Mail-Followup-To: ", fp);
1454 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1458 if (env->references) {
1459 fputs ("References:", fp);
1460 mutt_write_references (env->references, fp);
1464 /* Add the MIME headers */
1465 fputs ("MIME-Version: 1.0\n", fp);
1466 mutt_write_mime_header (attach, fp);
1469 if (env->in_reply_to) {
1470 fputs ("In-Reply-To:", fp);
1471 mutt_write_references (env->in_reply_to, fp);
1475 /* Add any user defined headers */
1476 for (; tmp; tmp = tmp->next) {
1477 if ((p = strchr (tmp->data, ':'))) {
1478 p = vskipspaces(p + 1);
1480 continue; /* don't emit empty fields. */
1482 /* check to see if the user has overridden the user-agent field */
1483 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1489 fputs (tmp->data, fp);
1494 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1495 if (MCore.operating_system) {
1496 fprintf(fp, "User-Agent: %s (%s)\n", mutt_make_version(),
1497 MCore.operating_system);
1499 fprintf(fp, "User-Agent: %s\n", mutt_make_version());
1503 return (ferror (fp) == 0 ? 0 : -1);
1506 static void encode_headers (string_list_t * h)
1512 for (; h; h = h->next) {
1513 if (!(p = strchr (h->data, ':')))
1517 p = vskipspaces(p + 1);
1523 rfc2047_encode_string (&tmp);
1524 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1526 sprintf (h->data + i, ": %s", NONULL (tmp));
1532 const char *mutt_fqdn(short may_hide_host)
1536 if (MCore.hostname && MCore.hostname[0] != '@') {
1539 if (may_hide_host && option (OPTHIDDENHOST)) {
1540 if ((p = strchr(MCore.hostname, '.')))
1543 /* sanity check: don't hide the host if
1544 the fqdn is something like detebe.org. */
1546 if (!p || !(q = strchr(p, '.')))
1554 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1556 #define APPEND_FMT(fmt, arg) \
1558 int snlen = snprintf(buf, len, fmt, arg); \
1563 #define APPEND_BYTE(c) \
1577 static char MsgIdPfx = 'A';
1581 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1582 APPEND_BYTE((isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.');
1590 APPEND_FMT("%02d", tm->tm_mday);
1593 APPEND_FMT("%02d", tm->tm_hour);
1596 APPEND_FMT("%02d", tm->tm_mon + 1);
1599 APPEND_FMT("%02d", tm->tm_min);
1602 APPEND_FMT("%lo", (unsigned long)now);
1605 APPEND_FMT("%u", (unsigned int)getpid());
1608 APPEND_FMT("%c", MsgIdPfx);
1609 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1612 APPEND_FMT("%u", (unsigned int)rand());
1615 APPEND_FMT("%x", (unsigned int)rand());
1618 APPEND_FMT("%02d", tm->tm_sec);
1621 APPEND_FMT("%u", (unsigned int) now);
1624 APPEND_FMT("%x", (unsigned int) now);
1626 case 'Y': /* this will break in the year 10000 ;-) */
1627 APPEND_FMT("%04d", tm->tm_year + 1900);
1632 default: /* invalid formats are replaced by '.' */
1643 static char *mutt_gen_msgid (void)
1646 char localpart[STRING];
1649 if (!(fqdn = mutt_fqdn(0)))
1650 fqdn = NONULL(MCore.shorthost);
1652 mutt_gen_localpart(localpart, sizeof(localpart), MsgIdFormat);
1653 snprintf(buf, sizeof(buf), "<%s@%s>", localpart, fqdn);
1654 return m_strdup(buf);
1657 static RETSIGTYPE alarm_handler (int sig __attribute__ ((unused)))
1662 /* invoke sendmail in a subshell
1663 path (in) path to program to execute
1664 args (in) arguments to pass to program
1665 msg (in) temp file containing message to send
1666 tempfile (out) if sendmail is put in the background, this points
1667 to the temporary file containing the stdout of the
1670 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1676 mutt_block_signals_system ();
1679 /* we also don't want to be stopped right now */
1680 sigaddset (&set, SIGTSTP);
1681 sigprocmask (SIG_BLOCK, &set, NULL);
1683 if (MTransport.sendmail_wait >= 0) {
1684 char tmp[_POSIX_PATH_MAX];
1687 *tempfile = m_strdup(tmp);
1690 if ((pid = fork ()) == 0) {
1691 struct sigaction act, oldalrm;
1693 /* save parent's ID before setsid() */
1696 /* we want the delivery to continue even after the main process dies,
1697 * so we put ourselves into another session right away
1701 /* next we close all open files */
1702 for (fd = 0; fd < getdtablesize(); fd++)
1705 /* now the second fork() */
1706 if ((pid = fork ()) == 0) {
1707 /* "msg" will be opened as stdin */
1708 if (open (msg, O_RDONLY, 0) < 0) {
1714 if (MTransport.sendmail_wait >= 0) {
1715 /* *tempfile will be opened as stdout */
1716 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1719 /* redirect stderr to *tempfile too */
1723 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1725 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1729 execv (path, (char**)args);
1732 else if (pid == -1) {
1738 /* sendmail_wait > 0: interrupt waitpid() after sendmail_wait seconds
1739 * sendmail_wait = 0: wait forever
1740 * sendmail_wait < 0: don't wait
1742 if (MTransport.sendmail_wait > 0) {
1744 act.sa_handler = alarm_handler;
1746 /* need to make sure waitpid() is interrupted on SIGALRM */
1747 act.sa_flags = SA_INTERRUPT;
1751 sigemptyset (&act.sa_mask);
1752 sigaction (SIGALRM, &act, &oldalrm);
1753 alarm (MTransport.sendmail_wait);
1755 else if (MTransport.sendmail_wait < 0)
1756 _exit (0xff & EX_OK);
1758 if (waitpid (pid, &st, 0) > 0) {
1759 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1760 if (MTransport.sendmail_wait && st == (0xff & EX_OK)) {
1761 unlink (*tempfile); /* no longer needed */
1765 st = (MTransport.sendmail_wait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1766 if (MTransport.sendmail_wait > 0) {
1772 /* reset alarm; not really needed, but... */
1774 sigaction (SIGALRM, &oldalrm, NULL);
1776 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1777 /* the parent is already dead */
1785 sigprocmask (SIG_UNBLOCK, &set, NULL);
1787 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1788 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1790 st = S_ERR; /* error */
1792 mutt_unblock_signals_system (1);
1797 static const char **
1798 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1800 for (; addr; addr = addr->next) {
1801 /* weed out group mailboxes, since those are for display only */
1802 if (addr->mailbox && !addr->group) {
1803 if (*argslen == *argsmax)
1804 p_realloc(&args, *argsmax += 5);
1805 args[(*argslen)++] = addr->mailbox;
1811 static const char **
1812 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1814 if (*argslen == *argsmax) {
1815 p_realloc(&args, *argsmax += 5);
1817 args[(*argslen)++] = s;
1821 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1822 address_t * to, address_t * cc, address_t * bcc, /* recips */
1823 const char *msg, /* file containing message */
1825 { /* message contains 8bit chars */
1826 char cmd[LONG_STRING];
1827 char *ps = NULL, *path = NULL, *childout = NULL;
1828 const char **args = NULL;
1829 ssize_t argslen = 0, argsmax = 0;
1833 if (option (OPTNEWSSEND)) {
1834 m_strformat(cmd, sizeof(cmd), 0, Inews, nntp_format_str, 0, 0);
1835 if (m_strisempty(cmd)) {
1836 i = nntp_post (msg);
1843 m_strcpy(cmd, sizeof(cmd), MTransport.sendmail);
1848 while ((ps = strtok(ps, " "))) {
1849 if (argslen == argsmax)
1850 p_realloc(&args, argsmax += 5);
1853 args[argslen++] = ps;
1855 path = m_strdup(ps);
1856 ps = strrchr (ps, '/');
1861 args[argslen++] = ps;
1868 if (!option (OPTNEWSSEND)) {
1870 if (eightbit && MTransport.use_8bitmime)
1871 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1873 if (MTransport.use_envelope_from) {
1874 address_t *f = MTransport.envelope_from_address;
1875 if (!f && from && !from->next)
1878 args = add_option (args, &argslen, &argsmax, "-f");
1879 args = add_args (args, &argslen, &argsmax, f);
1882 if (MTransport.dsn_notify) {
1883 args = add_option (args, &argslen, &argsmax, "-N");
1884 args = add_option (args, &argslen, &argsmax, MTransport.dsn_notify);
1886 if (MTransport.dsn_return) {
1887 args = add_option (args, &argslen, &argsmax, "-R");
1888 args = add_option (args, &argslen, &argsmax, MTransport.dsn_return);
1890 args = add_option (args, &argslen, &argsmax, "--");
1891 args = add_args (args, &argslen, &argsmax, to);
1892 args = add_args (args, &argslen, &argsmax, cc);
1893 args = add_args (args, &argslen, &argsmax, bcc);
1898 if (argslen >= argsmax)
1899 p_realloc(&args, ++argsmax);
1901 args[argslen++] = NULL;
1903 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
1905 mutt_error (_("Error sending message, child exited %d (%s)."), i,
1910 if (!stat(childout, &st) && st.st_size > 0)
1911 mutt_do_pager(_("Output of the delivery process"), childout, 0,
1919 p_delete(&childout);
1923 if (i == (EX_OK & 0xff))
1925 else if (i == S_BKG)
1932 int mutt_invoke_mta (address_t * from, /* the sender */
1933 address_t * to, address_t * cc, address_t * bcc, /* recips */
1934 const char *msg, /* file containing message */
1936 { /* message contains 8bit chars */
1939 if (!option (OPTNEWSSEND))
1942 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
1945 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
1948 /* For postponing (!final) do the necessary encodings only */
1949 void mutt_prepare_envelope (ENVELOPE * env, int final)
1952 if (env->bcc && !(env->to || env->cc)) {
1953 /* some MTA's will put an Apparently-To: header field showing the Bcc:
1954 * recipients if there is no To: or Cc: field, so attempt to suppress
1955 * it by using an empty To: field.
1957 env->to = address_new();
1959 env->to->next = address_new();
1960 env->to->mailbox = m_strdup("undisclosed-recipients");
1963 mutt_set_followup_to(env);
1965 if (!env->message_id && !m_strisempty(MsgIdFormat))
1966 env->message_id = mutt_gen_msgid();
1969 /* Take care of 8-bit => 7-bit conversion. */
1970 rfc2047_encode_adrlist(env->to, "To");
1971 rfc2047_encode_adrlist(env->cc, "Cc");
1972 rfc2047_encode_adrlist(env->bcc, "Bcc");
1973 rfc2047_encode_adrlist(env->from, "From");
1974 rfc2047_encode_adrlist(env->mail_followup_to, "Mail-Followup-To");
1975 rfc2047_encode_adrlist(env->reply_to, "Reply-To");
1978 rfc2047_encode_string (&env->subject);
1979 encode_headers (env->userhdrs);
1982 void mutt_unprepare_envelope (ENVELOPE * env)
1984 string_list_t *item;
1986 for (item = env->userhdrs; item; item = item->next)
1987 rfc2047_decode(&item->data);
1989 address_list_wipe(&env->mail_followup_to);
1991 /* back conversions */
1992 rfc2047_decode_adrlist(env->to);
1993 rfc2047_decode_adrlist(env->cc);
1994 rfc2047_decode_adrlist(env->bcc);
1995 rfc2047_decode_adrlist(env->from);
1996 rfc2047_decode_adrlist(env->reply_to);
1997 rfc2047_decode(&env->subject);
2000 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2001 const char *resent_from, address_t * env_from)
2005 char date[STRING], tempfile[_POSIX_PATH_MAX];
2006 MESSAGE *msg = NULL;
2009 /* Try to bounce each message out, aborting if we get any failures. */
2010 for (i = 0; i < Context->msgcount; i++)
2011 if (Context->hdrs[i]->tagged)
2013 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2018 /* If we failed to open a message, return with error */
2019 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2025 f = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
2027 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2029 if (!option (OPTBOUNCEDELIVERED))
2030 ch_flags |= CH_WEED_DELIVERED;
2032 fseeko (fp, h->offset, 0);
2033 fprintf (f, "Resent-From: %s", resent_from);
2034 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2035 if (!m_strisempty(MsgIdFormat))
2036 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid());
2037 fputs ("Resent-To: ", f);
2038 mutt_write_address_list (to, f, 11, 0);
2039 mutt_copy_header (fp, h, f, ch_flags, NULL);
2041 mutt_copy_bytes (fp, f, h->content->length);
2044 ret = mutt_invoke_mta(env_from, to, NULL, NULL, tempfile,
2045 h->content->encoding == ENC8BIT);
2049 mx_close_message (&msg);
2054 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2057 char resent_from[STRING];
2061 resent_from[0] = '\0';
2062 from = mutt_default_from ();
2064 rfc822_qualify(from, mutt_fqdn(1));
2066 rfc2047_encode_adrlist(from, "Resent-From");
2067 if (mutt_addrlist_to_idna (from, &err)) {
2068 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2071 rfc822_addrcat(resent_from, sizeof(resent_from), from, 0);
2074 unset_option (OPTNEWSSEND);
2077 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2079 address_list_wipe(&from);
2084 static void set_noconv_flags (BODY * b, short flag)
2086 for (; b; b = b->next) {
2087 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2088 set_noconv_flags (b->parts, flag);
2089 else if (b->type == TYPETEXT && b->noconv) {
2090 parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2095 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2096 int post, char *fcc)
2100 char tempfile[_POSIX_PATH_MAX];
2101 FILE *tempfp = NULL;
2105 set_noconv_flags (hdr->content, 1);
2107 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2111 /* We need to add a Content-Length field to avoid problems where a line in
2112 * the message body begins with "From "
2114 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2115 tempfp = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
2117 mutt_error(_("Could not create temporary file"));
2118 mx_close_mailbox (&f, NULL);
2123 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2124 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2125 mx_close_mailbox (&f, NULL);
2129 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2130 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2132 mutt_write_rfc822_header(msg->fp, hdr->env, hdr->content, -post, 0);
2134 /* (postponment) if this was a reply of some sort, <msgid> contians the
2135 * Message-ID: of message replied to. Save it using a special X-Mutt-
2136 * header so it can be picked up if the message is recalled at a later
2137 * point in time. This will allow the message to be marked as replied if
2138 * the same mailbox is still open.
2141 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2143 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2144 * it can be picked up when the message is recalled
2147 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2148 fprintf (msg->fp, "Status: RO\n");
2150 /* (postponment) if the mail is to be signed or encrypted, save this info */
2151 if (post && (hdr->security & APPLICATION_PGP)) {
2152 fputs ("X-Mutt-PGP: ", msg->fp);
2153 if (hdr->security & ENCRYPT)
2154 fputc ('E', msg->fp);
2155 if (hdr->security & SIGN) {
2156 fputc ('S', msg->fp);
2157 if (PgpSignAs && *PgpSignAs)
2158 fprintf (msg->fp, "<%s>", PgpSignAs);
2160 if (hdr->security & INLINE)
2161 fputc ('I', msg->fp);
2162 fputc ('\n', msg->fp);
2165 /* (postponment) if the mail is to be signed or encrypted, save this info */
2166 if (post && (hdr->security & APPLICATION_SMIME)) {
2167 fputs ("X-Mutt-SMIME: ", msg->fp);
2168 if (hdr->security & ENCRYPT) {
2169 fputc ('E', msg->fp);
2170 if (SmimeCryptAlg && *SmimeCryptAlg)
2171 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2173 if (hdr->security & SIGN) {
2174 fputc ('S', msg->fp);
2175 if (SmimeDefaultKey && *SmimeDefaultKey)
2176 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2178 if (hdr->security & INLINE)
2179 fputc ('I', msg->fp);
2180 fputc ('\n', msg->fp);
2183 /* (postponement) if the mail is to be sent through a mixmaster
2184 * chain, save that information
2186 if (post && hdr->chain && hdr->chain) {
2189 fputs ("X-Mutt-Mix:", msg->fp);
2190 for (p = hdr->chain; p; p = p->next)
2191 fprintf (msg->fp, " %s", (char *) p->data);
2193 fputc ('\n', msg->fp);
2197 char sasha[LONG_STRING];
2200 mutt_write_mime_body (hdr->content, tempfp);
2202 /* make sure the last line ends with a newline. Emacs doesn't ensure
2203 * this will happen, and it can cause problems parsing the mailbox
2206 fseeko (tempfp, -1, 2);
2207 if (fgetc (tempfp) != '\n') {
2208 fseeko (tempfp, 0, 2);
2209 fputc ('\n', tempfp);
2213 if (ferror (tempfp)) {
2216 mx_commit_message (msg, &f); /* XXX - really? */
2217 mx_close_message (&msg);
2218 mx_close_mailbox (&f, NULL);
2222 /* count the number of lines */
2224 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2226 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2227 fprintf (msg->fp, "Lines: %d\n\n", lines);
2229 /* copy the body and clean up */
2231 r = mutt_copy_stream (tempfp, msg->fp);
2232 if (m_fclose(&tempfp) != 0)
2234 /* if there was an error, leave the temp version */
2238 fputc ('\n', msg->fp); /* finish off the header */
2239 r = mutt_write_mime_body (hdr->content, msg->fp);
2242 if (mx_commit_message (msg, &f) != 0)
2244 mx_close_message (&msg);
2245 mx_close_mailbox (&f, NULL);
2248 set_noconv_flags (hdr->content, 0);