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)) {
1110 if (!crypt_valid_passphrase (hdr->security))
1114 fp = m_tempfile(buffer, sizeof(buffer), NONULL(MCore.tmpdir), NULL);
1119 body->type = TYPEMESSAGE;
1120 body->subtype = m_strdup("rfc822");
1121 body->filename = m_strdup(buffer);
1124 body->disposition = DISPINLINE;
1127 mutt_parse_mime_message (ctx, hdr);
1132 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1133 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1134 chflags |= CH_MIME | CH_TXTPLAIN;
1135 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1136 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1138 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1139 if (mutt_is_multipart_encrypted (hdr->content)) {
1140 chflags |= CH_MIME | CH_NONEWLINE;
1141 cmflags = M_CM_DECODE_PGP;
1144 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1145 chflags |= CH_MIME | CH_TXTPLAIN;
1146 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1149 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1150 chflags |= CH_MIME | CH_TXTPLAIN;
1151 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1152 pgp &= ~SMIMEENCRYPT;
1156 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1161 body->hdr = header_new();
1162 body->hdr->offset = 0;
1163 /* we don't need the user headers here */
1164 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1165 body->hdr->security = pgp;
1166 mutt_update_encoding (body);
1167 body->parts = body->hdr->content;
1174 BODY *mutt_make_file_attach (const char *path)
1180 att->filename = m_strdup(path);
1182 /* Attempt to determine the appropriate content-type based on the filename
1185 mutt_lookup_mime_type (att, path);
1187 if ((info = mutt_get_content_info (path, att)) == NULL) {
1188 body_list_wipe(&att);
1192 if (!att->subtype) {
1193 if (info->lobin == 0
1194 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1196 * Statistically speaking, there should be more than 10% "lobin"
1197 * chars if this is really a binary file...
1199 att->type = TYPETEXT;
1200 att->subtype = m_strdup("plain");
1202 att->type = TYPEAPPLICATION;
1203 att->subtype = m_strdup("octet-stream");
1207 mutt_update_encoding (att);
1211 static int get_toplevel_encoding (BODY * a)
1215 for (; a; a = a->next) {
1216 if (a->encoding == ENCBINARY)
1219 if (a->encoding == ENC8BIT)
1226 BODY *mutt_make_multipart (BODY * b)
1231 new->type = TYPEMULTIPART;
1232 new->subtype = m_strdup("mixed");
1233 new->encoding = get_toplevel_encoding (b);
1234 parameter_set_boundary(&new->parameter);
1236 new->disposition = DISPINLINE;
1242 /* remove the multipart body if it exists */
1243 BODY *mutt_remove_multipart (BODY * b)
1256 char *mutt_make_date (char *s, ssize_t len)
1258 time_t t = time (NULL);
1259 struct tm *l = localtime (&t);
1260 time_t tz = mutt_local_tz (t);
1264 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1265 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1266 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1267 (int) tz / 60, (int) abs (tz) % 60);
1271 /* wrapper around mutt_write_address() so we can handle very large
1272 recipient lists without needing a huge temporary buffer in memory */
1274 mutt_write_address_list(address_t *addr, FILE *fp, int linelen, int display)
1279 char buf[LONG_STRING];
1280 int len = rfc822_addrcpy(buf, ssizeof(buf), addr, display);
1283 if (linelen + len > 74) {
1285 linelen = 8; /* tab is usually about 8 spaces... */
1287 if (addr->mailbox) {
1297 if (!addr->group && addr->next && addr->next->mailbox) {
1307 /* need to write the list in reverse because they are stored in reverse order
1308 * when parsed to speed up threading
1310 void mutt_write_references(string_list_t *r, FILE *f)
1312 string_list_t *refs[10];
1315 p_clear(refs, countof(refs));
1316 for (i = 0; i < countof(refs) && r; r = r->next) {
1321 fprintf(f, " %s", refs[i]->data);
1325 static int edit_header(int mode, const char *s)
1328 int slen = m_strlen(s);
1330 if (mode != 1 || option(OPTXMAILTO))
1333 p = skipspaces(EditorHeaders);
1335 if (!ascii_strncasecmp(p, s, slen) && p[slen - 1] == ':')
1337 p = skipspaces(p + slen);
1343 /* Note: all RFC2047 encoding should be done outside of this routine, except
1344 * for the "real name." This will allow this routine to be used more than
1345 * once, if necessary.
1347 * Likewise, all IDN processing should happen outside of this routine.
1349 * mode == 1 => "lite" mode (used for edit_hdrs)
1350 * mode == 0 => normal mode. write full header + MIME headers
1351 * mode == -1 => write just the envelope info (used for postponing messages)
1353 * privacy != 0 => will omit any headers which may identify the user.
1354 * Output generated is suitable for being sent through
1355 * anonymous remailer chains.
1358 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1359 int mode, int privacy)
1361 char buffer[LONG_STRING];
1363 string_list_t *tmp = env->userhdrs;
1364 int has_agent = 0; /* user defined user-agent header field exists */
1367 if (!option (OPTNEWSSEND))
1369 if (mode == 0 && !privacy)
1370 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1372 /* OPTUSEFROM is not consulted here so that we can still write a From:
1373 * field if the user sets it with the `my_hdr' command
1375 if (env->from && !privacy) {
1377 rfc822_addrcat(buffer, sizeof(buffer), env->from, 0);
1378 fprintf (fp, "From: %s\n", buffer);
1383 mutt_write_address_list (env->to, fp, 4, 0);
1387 if (!option (OPTNEWSSEND))
1389 if (edit_header(mode, "To:"))
1390 fputs ("To:\n", fp);
1394 mutt_write_address_list (env->cc, fp, 4, 0);
1398 if (!option (OPTNEWSSEND))
1400 if (edit_header(mode, "Cc:"))
1401 fputs ("Cc:\n", fp);
1404 if (mode != 0 || option (OPTWRITEBCC)) {
1405 fputs ("Bcc: ", fp);
1406 mutt_write_address_list (env->bcc, fp, 5, 0);
1411 if (!option (OPTNEWSSEND))
1413 if (edit_header(mode, "Bcc:"))
1414 fputs ("Bcc:\n", fp);
1417 if (env->newsgroups)
1418 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1419 else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Newsgroups:"))
1420 fputs ("Newsgroups:\n", fp);
1422 if (env->followup_to)
1423 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1424 else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Followup-To:"))
1425 fputs ("Followup-To:\n", fp);
1427 if (env->x_comment_to)
1428 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1429 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1430 edit_header(mode, "X-Comment-To:"))
1431 fputs ("X-Comment-To:\n", fp);
1435 fprintf (fp, "Subject: %s\n", env->subject);
1436 else if (mode == 1 && edit_header(mode, "Subject:"))
1437 fputs ("Subject:\n", fp);
1439 /* save message id if the user has set it */
1440 if (env->message_id && !privacy)
1441 fprintf (fp, "Message-ID: %s\n", env->message_id);
1443 if (env->reply_to) {
1444 fputs ("Reply-To: ", fp);
1445 mutt_write_address_list (env->reply_to, fp, 10, 0);
1447 else if (mode > 0 && edit_header(mode, "Reply-To:"))
1448 fputs ("Reply-To:\n", fp);
1450 if (env->mail_followup_to)
1452 if (!option (OPTNEWSSEND))
1455 fputs ("Mail-Followup-To: ", fp);
1456 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1460 if (env->references) {
1461 fputs ("References:", fp);
1462 mutt_write_references (env->references, fp);
1466 /* Add the MIME headers */
1467 fputs ("MIME-Version: 1.0\n", fp);
1468 mutt_write_mime_header (attach, fp);
1471 if (env->in_reply_to) {
1472 fputs ("In-Reply-To:", fp);
1473 mutt_write_references (env->in_reply_to, fp);
1477 /* Add any user defined headers */
1478 for (; tmp; tmp = tmp->next) {
1479 if ((p = strchr (tmp->data, ':'))) {
1480 p = vskipspaces(p + 1);
1482 continue; /* don't emit empty fields. */
1484 /* check to see if the user has overridden the user-agent field */
1485 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1491 fputs (tmp->data, fp);
1496 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1497 if (MCore.operating_system) {
1498 fprintf(fp, "User-Agent: %s (%s)\n", mutt_make_version(),
1499 MCore.operating_system);
1501 fprintf(fp, "User-Agent: %s\n", mutt_make_version());
1505 return (ferror (fp) == 0 ? 0 : -1);
1508 static void encode_headers (string_list_t * h)
1514 for (; h; h = h->next) {
1515 if (!(p = strchr (h->data, ':')))
1519 p = vskipspaces(p + 1);
1525 rfc2047_encode_string (&tmp);
1526 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1528 sprintf (h->data + i, ": %s", NONULL (tmp));
1534 const char *mutt_fqdn(short may_hide_host)
1538 if (MCore.hostname && MCore.hostname[0] != '@') {
1541 if (may_hide_host && option (OPTHIDDENHOST)) {
1542 if ((p = strchr(MCore.hostname, '.')))
1545 /* sanity check: don't hide the host if
1546 the fqdn is something like detebe.org. */
1548 if (!p || !(q = strchr(p, '.')))
1556 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1558 #define APPEND_FMT(fmt, arg) \
1560 int snlen = snprintf(buf, len, fmt, arg); \
1565 #define APPEND_BYTE(c) \
1579 static char MsgIdPfx = 'A';
1583 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1584 APPEND_BYTE((isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.');
1592 APPEND_FMT("%02d", tm->tm_mday);
1595 APPEND_FMT("%02d", tm->tm_hour);
1598 APPEND_FMT("%02d", tm->tm_mon + 1);
1601 APPEND_FMT("%02d", tm->tm_min);
1604 APPEND_FMT("%lo", (unsigned long)now);
1607 APPEND_FMT("%u", (unsigned int)getpid());
1610 APPEND_FMT("%c", MsgIdPfx);
1611 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1614 APPEND_FMT("%u", (unsigned int)rand());
1617 APPEND_FMT("%x", (unsigned int)rand());
1620 APPEND_FMT("%02d", tm->tm_sec);
1623 APPEND_FMT("%u", (unsigned int) now);
1626 APPEND_FMT("%x", (unsigned int) now);
1628 case 'Y': /* this will break in the year 10000 ;-) */
1629 APPEND_FMT("%04d", tm->tm_year + 1900);
1634 default: /* invalid formats are replaced by '.' */
1645 static char *mutt_gen_msgid (void)
1648 char localpart[STRING];
1651 if (!(fqdn = mutt_fqdn(0)))
1652 fqdn = NONULL(MCore.shorthost);
1654 mutt_gen_localpart(localpart, sizeof(localpart), MsgIdFormat);
1655 snprintf(buf, sizeof(buf), "<%s@%s>", localpart, fqdn);
1656 return m_strdup(buf);
1659 static RETSIGTYPE alarm_handler (int sig __attribute__ ((unused)))
1664 /* invoke sendmail in a subshell
1665 path (in) path to program to execute
1666 args (in) arguments to pass to program
1667 msg (in) temp file containing message to send
1668 tempfile (out) if sendmail is put in the background, this points
1669 to the temporary file containing the stdout of the
1672 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1678 mutt_block_signals_system ();
1681 /* we also don't want to be stopped right now */
1682 sigaddset (&set, SIGTSTP);
1683 sigprocmask (SIG_BLOCK, &set, NULL);
1685 if (MTransport.sendmail_wait >= 0) {
1686 char tmp[_POSIX_PATH_MAX];
1689 *tempfile = m_strdup(tmp);
1692 if ((pid = fork ()) == 0) {
1693 struct sigaction act, oldalrm;
1695 /* save parent's ID before setsid() */
1698 /* we want the delivery to continue even after the main process dies,
1699 * so we put ourselves into another session right away
1703 /* next we close all open files */
1704 for (fd = 0; fd < getdtablesize(); fd++)
1707 /* now the second fork() */
1708 if ((pid = fork ()) == 0) {
1709 /* "msg" will be opened as stdin */
1710 if (open (msg, O_RDONLY, 0) < 0) {
1716 if (MTransport.sendmail_wait >= 0) {
1717 /* *tempfile will be opened as stdout */
1718 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1721 /* redirect stderr to *tempfile too */
1725 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1727 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1731 execv (path, (char**)args);
1734 else if (pid == -1) {
1740 /* sendmail_wait > 0: interrupt waitpid() after sendmail_wait seconds
1741 * sendmail_wait = 0: wait forever
1742 * sendmail_wait < 0: don't wait
1744 if (MTransport.sendmail_wait > 0) {
1746 act.sa_handler = alarm_handler;
1748 /* need to make sure waitpid() is interrupted on SIGALRM */
1749 act.sa_flags = SA_INTERRUPT;
1753 sigemptyset (&act.sa_mask);
1754 sigaction (SIGALRM, &act, &oldalrm);
1755 alarm (MTransport.sendmail_wait);
1757 else if (MTransport.sendmail_wait < 0)
1758 _exit (0xff & EX_OK);
1760 if (waitpid (pid, &st, 0) > 0) {
1761 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1762 if (MTransport.sendmail_wait && st == (0xff & EX_OK)) {
1763 unlink (*tempfile); /* no longer needed */
1767 st = (MTransport.sendmail_wait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1768 if (MTransport.sendmail_wait > 0) {
1774 /* reset alarm; not really needed, but... */
1776 sigaction (SIGALRM, &oldalrm, NULL);
1778 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1779 /* the parent is already dead */
1787 sigprocmask (SIG_UNBLOCK, &set, NULL);
1789 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1790 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1792 st = S_ERR; /* error */
1794 mutt_unblock_signals_system (1);
1799 static const char **
1800 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1802 for (; addr; addr = addr->next) {
1803 /* weed out group mailboxes, since those are for display only */
1804 if (addr->mailbox && !addr->group) {
1805 if (*argslen == *argsmax)
1806 p_realloc(&args, *argsmax += 5);
1807 args[(*argslen)++] = addr->mailbox;
1813 static const char **
1814 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1816 if (*argslen == *argsmax) {
1817 p_realloc(&args, *argsmax += 5);
1819 args[(*argslen)++] = s;
1823 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1824 address_t * to, address_t * cc, address_t * bcc, /* recips */
1825 const char *msg, /* file containing message */
1827 { /* message contains 8bit chars */
1828 char cmd[LONG_STRING];
1829 char *ps = NULL, *path = NULL, *childout = NULL;
1830 const char **args = NULL;
1831 ssize_t argslen = 0, argsmax = 0;
1835 if (option (OPTNEWSSEND)) {
1836 m_strformat(cmd, sizeof(cmd), 0, Inews, nntp_format_str, 0, 0);
1837 if (m_strisempty(cmd)) {
1838 i = nntp_post (msg);
1845 m_strcpy(cmd, sizeof(cmd), MTransport.sendmail);
1850 while ((ps = strtok(ps, " "))) {
1851 if (argslen == argsmax)
1852 p_realloc(&args, argsmax += 5);
1855 args[argslen++] = ps;
1857 path = m_strdup(ps);
1858 ps = strrchr (ps, '/');
1863 args[argslen++] = ps;
1870 if (!option (OPTNEWSSEND)) {
1872 if (eightbit && MTransport.use_8bitmime)
1873 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1875 if (MTransport.use_envelope_from) {
1876 address_t *f = MTransport.envelope_from_address;
1877 if (!f && from && !from->next)
1880 args = add_option (args, &argslen, &argsmax, "-f");
1881 args = add_args (args, &argslen, &argsmax, f);
1884 if (MTransport.dsn_notify) {
1885 args = add_option (args, &argslen, &argsmax, "-N");
1886 args = add_option (args, &argslen, &argsmax, MTransport.dsn_notify);
1888 if (MTransport.dsn_return) {
1889 args = add_option (args, &argslen, &argsmax, "-R");
1890 args = add_option (args, &argslen, &argsmax, MTransport.dsn_return);
1892 args = add_option (args, &argslen, &argsmax, "--");
1893 args = add_args (args, &argslen, &argsmax, to);
1894 args = add_args (args, &argslen, &argsmax, cc);
1895 args = add_args (args, &argslen, &argsmax, bcc);
1900 if (argslen >= argsmax)
1901 p_realloc(&args, ++argsmax);
1903 args[argslen++] = NULL;
1905 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
1907 mutt_error (_("Error sending message, child exited %d (%s)."), i,
1912 if (!stat(childout, &st) && st.st_size > 0)
1913 mutt_do_pager(_("Output of the delivery process"), childout, 0,
1921 p_delete(&childout);
1925 if (i == (EX_OK & 0xff))
1927 else if (i == S_BKG)
1934 int mutt_invoke_mta (address_t * from, /* the sender */
1935 address_t * to, address_t * cc, address_t * bcc, /* recips */
1936 const char *msg, /* file containing message */
1938 { /* message contains 8bit chars */
1941 if (!option (OPTNEWSSEND))
1944 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
1947 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
1950 /* For postponing (!final) do the necessary encodings only */
1951 void mutt_prepare_envelope (ENVELOPE * env, int final)
1954 if (env->bcc && !(env->to || env->cc)) {
1955 /* some MTA's will put an Apparently-To: header field showing the Bcc:
1956 * recipients if there is no To: or Cc: field, so attempt to suppress
1957 * it by using an empty To: field.
1959 env->to = address_new();
1961 env->to->next = address_new();
1962 env->to->mailbox = m_strdup("undisclosed-recipients");
1965 mutt_set_followup_to(env);
1967 if (!env->message_id && !m_strisempty(MsgIdFormat))
1968 env->message_id = mutt_gen_msgid();
1971 /* Take care of 8-bit => 7-bit conversion. */
1972 rfc2047_encode_adrlist(env->to, "To");
1973 rfc2047_encode_adrlist(env->cc, "Cc");
1974 rfc2047_encode_adrlist(env->bcc, "Bcc");
1975 rfc2047_encode_adrlist(env->from, "From");
1976 rfc2047_encode_adrlist(env->mail_followup_to, "Mail-Followup-To");
1977 rfc2047_encode_adrlist(env->reply_to, "Reply-To");
1980 rfc2047_encode_string (&env->subject);
1981 encode_headers (env->userhdrs);
1984 void mutt_unprepare_envelope (ENVELOPE * env)
1986 string_list_t *item;
1988 for (item = env->userhdrs; item; item = item->next)
1989 rfc2047_decode(&item->data);
1991 address_list_wipe(&env->mail_followup_to);
1993 /* back conversions */
1994 rfc2047_decode_adrlist(env->to);
1995 rfc2047_decode_adrlist(env->cc);
1996 rfc2047_decode_adrlist(env->bcc);
1997 rfc2047_decode_adrlist(env->from);
1998 rfc2047_decode_adrlist(env->reply_to);
1999 rfc2047_decode(&env->subject);
2002 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2003 const char *resent_from, address_t * env_from)
2007 char date[STRING], tempfile[_POSIX_PATH_MAX];
2008 MESSAGE *msg = NULL;
2011 /* Try to bounce each message out, aborting if we get any failures. */
2012 for (i = 0; i < Context->msgcount; i++)
2013 if (Context->hdrs[i]->tagged)
2015 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2020 /* If we failed to open a message, return with error */
2021 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2027 f = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
2029 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2031 if (!option (OPTBOUNCEDELIVERED))
2032 ch_flags |= CH_WEED_DELIVERED;
2034 fseeko (fp, h->offset, 0);
2035 fprintf (f, "Resent-From: %s", resent_from);
2036 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2037 if (!m_strisempty(MsgIdFormat))
2038 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid());
2039 fputs ("Resent-To: ", f);
2040 mutt_write_address_list (to, f, 11, 0);
2041 mutt_copy_header (fp, h, f, ch_flags, NULL);
2043 mutt_copy_bytes (fp, f, h->content->length);
2046 ret = mutt_invoke_mta(env_from, to, NULL, NULL, tempfile,
2047 h->content->encoding == ENC8BIT);
2051 mx_close_message (&msg);
2056 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2059 char resent_from[STRING];
2063 resent_from[0] = '\0';
2064 from = mutt_default_from ();
2066 rfc822_qualify(from, mutt_fqdn(1));
2068 rfc2047_encode_adrlist(from, "Resent-From");
2069 if (mutt_addrlist_to_idna (from, &err)) {
2070 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2073 rfc822_addrcat(resent_from, sizeof(resent_from), from, 0);
2076 unset_option (OPTNEWSSEND);
2079 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2081 address_list_wipe(&from);
2086 static void set_noconv_flags (BODY * b, short flag)
2088 for (; b; b = b->next) {
2089 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2090 set_noconv_flags (b->parts, flag);
2091 else if (b->type == TYPETEXT && b->noconv) {
2092 parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2097 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2098 int post, char *fcc)
2102 char tempfile[_POSIX_PATH_MAX];
2103 FILE *tempfp = NULL;
2107 set_noconv_flags (hdr->content, 1);
2109 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2113 /* We need to add a Content-Length field to avoid problems where a line in
2114 * the message body begins with "From "
2116 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2117 tempfp = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
2119 mutt_error(_("Could not create temporary file"));
2120 mx_close_mailbox (&f, NULL);
2125 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2126 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2127 mx_close_mailbox (&f, NULL);
2131 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2132 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2134 mutt_write_rfc822_header(msg->fp, hdr->env, hdr->content, -post, 0);
2136 /* (postponment) if this was a reply of some sort, <msgid> contians the
2137 * Message-ID: of message replied to. Save it using a special X-Mutt-
2138 * header so it can be picked up if the message is recalled at a later
2139 * point in time. This will allow the message to be marked as replied if
2140 * the same mailbox is still open.
2143 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2145 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2146 * it can be picked up when the message is recalled
2149 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2150 fprintf (msg->fp, "Status: RO\n");
2152 /* (postponment) if the mail is to be signed or encrypted, save this info */
2153 if (post && (hdr->security & APPLICATION_PGP)) {
2154 fputs ("X-Mutt-PGP: ", msg->fp);
2155 if (hdr->security & ENCRYPT)
2156 fputc ('E', msg->fp);
2157 if (hdr->security & SIGN) {
2158 fputc ('S', msg->fp);
2159 if (PgpSignAs && *PgpSignAs)
2160 fprintf (msg->fp, "<%s>", PgpSignAs);
2162 if (hdr->security & INLINE)
2163 fputc ('I', msg->fp);
2164 fputc ('\n', msg->fp);
2167 /* (postponment) if the mail is to be signed or encrypted, save this info */
2168 if (post && (hdr->security & APPLICATION_SMIME)) {
2169 fputs ("X-Mutt-SMIME: ", msg->fp);
2170 if (hdr->security & ENCRYPT) {
2171 fputc ('E', msg->fp);
2172 if (SmimeCryptAlg && *SmimeCryptAlg)
2173 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2175 if (hdr->security & SIGN) {
2176 fputc ('S', msg->fp);
2177 if (SmimeDefaultKey && *SmimeDefaultKey)
2178 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2180 if (hdr->security & INLINE)
2181 fputc ('I', msg->fp);
2182 fputc ('\n', msg->fp);
2185 /* (postponement) if the mail is to be sent through a mixmaster
2186 * chain, save that information
2188 if (post && hdr->chain && hdr->chain) {
2191 fputs ("X-Mutt-Mix:", msg->fp);
2192 for (p = hdr->chain; p; p = p->next)
2193 fprintf (msg->fp, " %s", (char *) p->data);
2195 fputc ('\n', msg->fp);
2199 char sasha[LONG_STRING];
2202 mutt_write_mime_body (hdr->content, tempfp);
2204 /* make sure the last line ends with a newline. Emacs doesn't ensure
2205 * this will happen, and it can cause problems parsing the mailbox
2208 fseeko (tempfp, -1, 2);
2209 if (fgetc (tempfp) != '\n') {
2210 fseeko (tempfp, 0, 2);
2211 fputc ('\n', tempfp);
2215 if (ferror (tempfp)) {
2218 mx_commit_message (msg, &f); /* XXX - really? */
2219 mx_close_message (&msg);
2220 mx_close_mailbox (&f, NULL);
2224 /* count the number of lines */
2226 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2228 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2229 fprintf (msg->fp, "Lines: %d\n\n", lines);
2231 /* copy the body and clean up */
2233 r = mutt_copy_stream (tempfp, msg->fp);
2234 if (m_fclose(&tempfp) != 0)
2236 /* if there was an error, leave the temp version */
2240 fputc ('\n', msg->fp); /* finish off the header */
2241 r = mutt_write_mime_body (hdr->content, msg->fp);
2244 if (mx_commit_message (msg, &f) != 0)
2246 mx_close_message (&msg);
2247 mx_close_mailbox (&f, NULL);
2250 set_noconv_flags (hdr->content, 0);