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/enter.h>
19 #include <lib-ui/curses.h>
20 #include <lib-mx/mx.h>
25 #include "recvattach.h"
29 #include "mutt_idna.h"
35 #ifdef HAVE_SYSEXITS_H
37 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
41 static void transform_to_7bit (BODY * a, FILE * fpin);
43 static void encode_quoted (fgetconv_t * fc, FILE * fout, int istext)
46 char line[77], savechar;
48 while ((c = fgetconv (fc)) != EOF) {
49 /* Wrap the line if needed. */
50 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
51 /* If the last character is "quoted", then be sure to move all three
52 * characters to the next line. Otherwise, just move the last
55 if (line[linelen - 3] == '=') {
56 line[linelen - 3] = 0;
61 line[1] = line[linelen - 2];
62 line[2] = line[linelen - 1];
66 savechar = line[linelen - 1];
67 line[linelen - 1] = '=';
76 /* Escape lines that begin with/only contain "the message separator". */
77 if (linelen == 4 && !m_strncmp("From", line, 4)) {
78 m_strcpy(line, sizeof(line), "=46rom");
81 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
82 m_strcpy(line, sizeof(line), "=66rom");
85 else if (linelen == 1 && line[0] == '.') {
86 m_strcpy(line, sizeof(line), "=2E");
91 if (c == '\n' && istext) {
92 /* Check to make sure there is no trailing space on this line. */
94 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
96 sprintf (line + linelen - 1, "=%2.2X",
97 (unsigned char) line[linelen - 1]);
101 savechar = line[linelen - 1];
103 line[linelen - 1] = '=';
106 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
116 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
117 /* Check to make sure there is enough room for the quoted character.
118 * If not, wrap to the next line.
121 line[linelen++] = '=';
127 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
131 /* Don't worry about wrapping the line here. That will happen during
132 * the next iteration when I'll also know what the next character is.
138 /* Take care of anything left in the buffer */
140 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
141 /* take care of trailing whitespace */
143 sprintf (line + linelen - 1, "=%2.2X",
144 (unsigned char) line[linelen - 1]);
146 savechar = line[linelen - 1];
147 line[linelen - 1] = '=';
151 sprintf (line, "=%2.2X", (unsigned char) savechar);
160 static char b64_buffer[3];
161 static short b64_num;
162 static short b64_linelen;
164 static void b64_flush (FILE * fout)
171 if (b64_linelen >= 72) {
176 for (i = b64_num; i < 3; i++)
177 b64_buffer[i] = '\0';
179 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
182 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
187 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
191 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
196 while (b64_linelen % 4) {
205 static void b64_putc (char c, FILE * fout)
210 b64_buffer[b64_num++] = c;
214 static void encode_base64 (fgetconv_t * fc, FILE * fout, int istext)
218 b64_num = b64_linelen = 0;
220 while ((ch = fgetconv (fc)) != EOF) {
221 if (istext && ch == '\n' && ch1 != '\r')
222 b64_putc ('\r', fout);
230 static void encode_8bit (fgetconv_t * fc, FILE * fout,
231 int istext __attribute__ ((unused)))
235 while ((ch = fgetconv (fc)) != EOF)
240 int mutt_write_mime_header (BODY * a, FILE * f)
249 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
254 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
256 for (p = a->parameter; p; p = p->next) {
265 tmp = m_strdup(p->value);
266 encode = rfc2231_encode_string (&tmp);
267 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
269 /* Dirty hack to make messages readable by Outlook Express
270 * for the Mac: force quotes around the boundary parameter
271 * even when they aren't needed.
274 if (!ascii_strcasecmp (p->attribute, "boundary")
275 && !strcmp (buffer, tmp))
276 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
280 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
282 if (len + tmplen + 2 > 76) {
291 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
299 fprintf (f, "Content-Description: %s\n", a->description);
301 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
302 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
305 if (!(fn = a->d_filename))
311 /* Strip off the leading path... */
312 if ((t = strrchr (fn, '/')))
319 encode = rfc2231_encode_string (&tmp);
320 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
322 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
328 if (a->encoding != ENC7BIT)
329 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
331 /* Do NOT add the terminator here!!! */
332 return (ferror (f) ? -1 : 0);
335 int mutt_write_mime_body (BODY * a, FILE * f)
338 char boundary[STRING];
339 char send_charset[STRING];
344 if (a->type == TYPEMULTIPART) {
345 /* First, find the boundary to use */
346 if (!(p = parameter_getval(a->parameter, "boundary"))) {
347 mutt_error _("No boundary parameter found! [report this error]");
351 m_strcpy(boundary, sizeof(boundary), p);
353 for (t = a->parts; t; t = t->next) {
354 fprintf (f, "\n--%s\n", boundary);
355 if (mutt_write_mime_header (t, f) == -1)
358 if (mutt_write_mime_body (t, f) == -1)
361 fprintf (f, "\n--%s--\n", boundary);
362 return (ferror (f) ? -1 : 0);
365 /* This is pretty gross, but it's the best solution for now... */
366 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
367 fputs ("Version: 1\n", f);
371 if ((fpin = fopen (a->filename, "r")) == NULL) {
372 mutt_error (_("%s no longer exists!"), a->filename);
376 if (a->type == TYPETEXT && (!a->noconv))
377 fc = fgetconv_open (fpin, a->file_charset,
378 mutt_get_body_charset (send_charset,
379 sizeof (send_charset), a), 0);
381 fc = fgetconv_open (fpin, 0, 0, 0);
383 #define write_as_text_part(a) (mutt_is_text_part(a) || mutt_is_application_pgp(a))
384 if (a->encoding == ENCQUOTEDPRINTABLE)
385 encode_quoted (fc, f, write_as_text_part (a));
386 else if (a->encoding == ENCBASE64)
387 encode_base64 (fc, f, write_as_text_part (a));
388 else if (a->type == TYPETEXT && (!a->noconv))
389 encode_8bit (fc, f, write_as_text_part (a));
391 mutt_copy_stream (fpin, f);
392 #undef write_as_text_part
394 fgetconv_close (&fc);
397 return (ferror (f) ? -1 : 0);
409 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
413 int whitespace = s->whitespace;
415 int linelen = s->linelen;
416 int was_cr = s->was_cr;
418 if (!d) { /* This signals EOF */
421 if (linelen > info->linemax)
422 info->linemax = linelen;
427 for (; dlen; d++, dlen--) {
440 if (linelen > info->linemax)
441 info->linemax = linelen;
456 if (linelen > info->linemax)
457 info->linemax = linelen;
462 else if (ch == '\r') {
470 else if (ch == '\t' || ch == '\f') {
474 else if (ch < 32 || ch == 127)
478 if ((ch == 'F') || (ch == 'f'))
488 if (linelen == 2 && ch != 'r')
490 else if (linelen == 3 && ch != 'o')
492 else if (linelen == 4) {
505 if (ch != ' ' && ch != '\t')
510 s->whitespace = whitespace;
512 s->linelen = linelen;
518 * Find the best charset conversion of the file from fromcode into one
519 * of the tocodes. If successful, set *tocode and CONTENT *info and
520 * return the number of characters converted inexactly. If no
521 * conversion was possible, return -1.
523 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
524 * which would otherwise prevent us from knowing the number of inexact
525 * conversions. Where the candidate target charset is UTF-8 we avoid
526 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
527 * fails with some libraries.
529 * We assume that the output from iconv is never more than 4 times as
530 * long as the input for any pair of charsets we might be interested
533 static ssize_t convert_file_to (FILE * file, const char *fromcode,
534 int ncodes, const char **tocodes,
535 int *tocode, CONTENT * info)
538 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
541 ssize_t ibl, obl, ubl, ubl1, n, ret;
544 CONTENT_STATE *states;
547 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
548 if (cd1 == MUTT_ICONV_ERROR)
551 cd = p_new(iconv_t, ncodes);
552 score = p_new(ssize_t, ncodes);
553 states = p_new(CONTENT_STATE, ncodes);
554 infos = p_new(CONTENT, ncodes);
556 for (i = 0; i < ncodes; i++)
557 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
558 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
560 /* Special case for conversion to UTF-8 */
561 cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
567 /* Try to fill input buffer */
568 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
571 /* Convert to UTF-8 */
573 ob = bufu, obl = sizeof (bufu);
574 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
575 if (n == -1 && ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
581 /* Convert from UTF-8 */
582 for (i = 0; i < ncodes; i++)
583 if (cd[i] != MUTT_ICONV_ERROR && score[i] != -1) {
584 ub = bufu, ubl = ubl1;
585 ob = bufo, obl = sizeof (bufo);
586 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
592 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
595 else if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1)
596 /* Special case for conversion to UTF-8 */
597 update_content_info (&infos[i], &states[i], bufu, ubl1);
600 /* Save unused input */
601 memmove (bufi, ib, ibl);
602 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
609 /* Find best score */
611 for (i = 0; i < ncodes; i++) {
612 if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1) {
613 /* Special case for conversion to UTF-8 */
618 else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
620 else if (ret == -1 || score[i] < ret) {
628 memcpy (info, &infos[*tocode], sizeof (CONTENT));
629 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
633 for (i = 0; i < ncodes; i++)
634 if (cd[i] != MUTT_ICONV_ERROR)
647 * Find the first of the fromcodes that gives a valid conversion and
648 * the best charset conversion of the file into one of the tocodes. If
649 * successful, set *fromcode and *tocode to dynamically allocated
650 * strings, set CONTENT *info, and return the number of characters
651 * converted inexactly. If no conversion was possible, return -1.
653 * Both fromcodes and tocodes may be colon-separated lists of charsets.
654 * However, if fromcode is zero then fromcodes is assumed to be the
655 * name of a single charset even if it contains a colon.
657 static ssize_t convert_file_from_to (FILE * file,
658 const char *fromcodes,
659 const char *tocodes, char **fromcode,
660 char **tocode, CONTENT * info)
668 /* Count the tocodes */
670 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
671 if ((c1 = strchr (c, ':')) == c)
677 tcode = p_new(char *, ncodes);
678 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
679 if ((c1 = strchr (c, ':')) == c)
681 tcode[i] = m_substrdup(c, c1);
686 /* Try each fromcode in turn */
687 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
688 if ((c1 = strchr (c, ':')) == c)
690 fcode = m_substrdup(c, c1);
692 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
704 /* There is only one fromcode */
705 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
714 for (i = 0; i < ncodes; i++)
723 * Analyze the contents of a file to determine which MIME encoding to use.
724 * Also set the body charset, sometimes, or not.
726 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
731 char *fromcode = NULL;
742 if (stat (fname, &sb) == -1) {
743 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
747 if (!S_ISREG (sb.st_mode)) {
748 mutt_error (_("%s isn't a regular file."), fname);
752 if ((fp = fopen (fname, "r")) == NULL) {
756 info = p_new(CONTENT, 1);
759 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
760 const char *chs = parameter_getval(b->parameter, "charset");
761 char *fchs = b->use_disp && !m_strisempty(mod_cset.file_charset)
762 ? FileCharset : mod_cset.charset;
763 if (mod_cset.charset && (chs || mod_cset.send_charset) &&
764 convert_file_from_to (fp, fchs, chs ? chs : mod_cset.send_charset,
765 &fromcode, &tocode, info) != -1) {
767 charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
768 parameter_setval(&b->parameter, "charset", chsbuf);
770 b->file_charset = fromcode;
778 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
779 update_content_info (info, &state, buffer, r);
780 update_content_info (info, &state, 0, 0);
784 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
785 parameter_setval(&b->parameter, "charset",
786 (!info->hibin ? "us-ascii"
787 : mod_cset.charset && !charset_is_us_ascii(mod_cset.charset)
788 ? mod_cset.charset : "unknown-8bit"));
793 /* Given a file with path ``s'', see if there is a registered MIME type.
794 * returns the major MIME type, and copies the subtype to ``d''. First look
795 * for ~/.mime.types, then look in a system mime.types if we can find one.
796 * The longest match is used so that we can match `ps.gz' when `gz' also
800 int mutt_lookup_mime_type (BODY * att, const char *path)
804 char buf[LONG_STRING];
805 char subtype[STRING], xtype[STRING];
807 int szf, sze, cur_sze;
815 szf = m_strlen(path);
817 for (count = 0; count < 4; count++) {
819 * can't use strtok() because we use it in an inner loop below, so use
820 * a switch statement here instead.
824 snprintf(buf, sizeof (buf), "%s/.mime.types", NONULL(mod_core.homedir));
827 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
830 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
833 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
836 goto bye; /* shouldn't happen */
839 if ((f = fopen (buf, "r")) != NULL) {
840 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
841 /* weed out any comments */
842 if ((p = strchr (buf, '#')))
845 /* remove any leading space. */
846 ct = vskipspaces(buf);
848 /* position on the next field in this line */
849 if ((p = strpbrk (ct, " \t")) == NULL)
854 /* cycle through the file extensions */
855 while ((p = strtok (p, " \t\n"))) {
857 if ((sze > cur_sze) && (szf >= sze) &&
858 (m_strcasecmp(path + szf - sze, p) == 0
859 || ascii_strcasecmp (path + szf - sze, p) == 0)
860 && (szf == sze || path[szf - sze - 1] == '.'))
862 /* get the content-type */
864 if ((p = strchr (ct, '/')) == NULL) {
865 /* malformed line, just skip it. */
870 for (q = p; *q && !ISSPACE (*q); q++);
872 m_strncpy(subtype, sizeof(subtype), p, q - p);
874 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
875 m_strcpy(xtype, sizeof(xtype), ct);
888 if (type != TYPEOTHER || *xtype != '\0') {
890 m_strreplace(&att->subtype, subtype);
891 m_strreplace(&att->xtype, xtype);
897 void mutt_message_to_7bit (BODY * a, FILE * fp)
899 char temp[_POSIX_PATH_MAX];
905 if (!a->filename && fp)
907 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
908 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
913 if (stat (a->filename, &sb) == -1) {
914 mutt_perror ("stat");
917 a->length = sb.st_size;
920 fpout = m_tempfile(temp, sizeof(temp), NONULL(mod_core.tmpdir), NULL);
922 mutt_error(_("Could not create temporary file"));
926 fseeko (fpin, a->offset, 0);
927 a->parts = mutt_parse_messageRFC822 (fpin, a);
929 transform_to_7bit (a->parts, fpin);
931 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
932 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
934 fputs ("MIME-Version: 1.0\n", fpout);
935 mutt_write_mime_header (a->parts, fpout);
937 mutt_write_mime_body (a->parts, fpout);
949 a->encoding = ENC7BIT;
950 a->d_filename = a->filename;
951 if (a->filename && a->unlink)
952 unlink (a->filename);
953 a->filename = m_strdup(temp);
955 if (stat (a->filename, &sb) == -1) {
956 mutt_perror ("stat");
959 a->length = sb.st_size;
960 body_list_wipe(&a->parts);
961 a->hdr->content = NULL;
964 static void transform_to_7bit (BODY * a, FILE * fpin)
966 char buff[_POSIX_PATH_MAX];
971 for (; a; a = a->next) {
972 if (a->type == TYPEMULTIPART) {
973 if (a->encoding != ENC7BIT)
974 a->encoding = ENC7BIT;
976 transform_to_7bit (a->parts, fpin);
978 else if (mutt_is_message_type(a)) {
979 mutt_message_to_7bit (a, fpin);
983 a->force_charset = 1;
985 s.fpout = m_tempfile(buff, sizeof(buff), NONULL(mod_core.tmpdir), NULL);
987 mutt_error(_("Could not create temporary file"));
991 mutt_decode_attachment (a, &s);
993 a->d_filename = a->filename;
994 a->filename = m_strdup(buff);
996 if (stat (a->filename, &sb) == -1) {
997 mutt_perror ("stat");
1000 a->length = sb.st_size;
1002 mutt_update_encoding (a);
1003 if (a->encoding == ENC8BIT)
1004 a->encoding = ENCQUOTEDPRINTABLE;
1005 else if (a->encoding == ENCBINARY)
1006 a->encoding = ENCBASE64;
1011 /* determine which Content-Transfer-Encoding to use */
1012 static void mutt_set_encoding (BODY * b, CONTENT * info)
1014 char send_charset[STRING];
1016 if (b->type == TYPETEXT) {
1018 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1019 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1020 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1021 b->encoding = ENCQUOTEDPRINTABLE;
1022 else if (info->hibin)
1023 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1025 b->encoding = ENC7BIT;
1027 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1028 if (info->lobin || info->hibin) {
1029 if (option (OPTALLOW8BIT) && !info->lobin)
1030 b->encoding = ENC8BIT;
1032 mutt_message_to_7bit (b, NULL);
1035 b->encoding = ENC7BIT;
1037 else if (b->type == TYPEAPPLICATION
1038 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1039 b->encoding = ENC7BIT;
1042 /* Determine which encoding is smaller */
1043 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1044 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1045 b->encoding = ENCBASE64;
1047 b->encoding = ENCQUOTEDPRINTABLE;
1051 void mutt_stamp_attachment (BODY * a)
1053 a->stamp = time (NULL);
1056 /* Get a body's character set */
1058 char *mutt_get_body_charset(char *d, ssize_t dlen, BODY * b)
1062 if (b && b->type != TYPETEXT)
1065 p = b ? parameter_getval(b->parameter, "charset") : NULL;
1066 charset_canonicalize(d, dlen, p);
1071 /* Assumes called from send mode where BODY->filename points to actual file */
1072 void mutt_update_encoding (BODY * a)
1075 char chsbuff[STRING];
1077 /* override noconv when it's us-ascii */
1078 if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1081 if (!a->force_charset && !a->noconv)
1082 parameter_delval(&a->parameter, "charset");
1084 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1087 mutt_set_encoding (a, info);
1088 mutt_stamp_attachment (a);
1090 p_delete(&a->content);
1095 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1097 char buffer[LONG_STRING];
1100 int cmflags, chflags;
1101 int pgp = hdr->security;
1103 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1104 (hdr->security & ENCRYPT)) {
1107 fp = m_tempfile(buffer, sizeof(buffer), NONULL(mod_core.tmpdir), NULL);
1112 body->type = TYPEMESSAGE;
1113 body->subtype = m_strdup("rfc822");
1114 body->filename = m_strdup(buffer);
1117 body->disposition = DISPINLINE;
1120 mutt_parse_mime_message (ctx, hdr);
1125 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1126 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1127 chflags |= CH_MIME | CH_TXTPLAIN;
1128 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1129 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1131 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1132 if (mutt_is_multipart_encrypted (hdr->content)) {
1133 chflags |= CH_MIME | CH_NONEWLINE;
1134 cmflags = M_CM_DECODE_PGP;
1137 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1138 chflags |= CH_MIME | CH_TXTPLAIN;
1139 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1142 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1143 chflags |= CH_MIME | CH_TXTPLAIN;
1144 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1145 pgp &= ~SMIMEENCRYPT;
1149 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1154 body->hdr = header_new();
1155 body->hdr->offset = 0;
1156 /* we don't need the user headers here */
1157 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1158 body->hdr->security = pgp;
1159 mutt_update_encoding (body);
1160 body->parts = body->hdr->content;
1167 BODY *mutt_make_file_attach (const char *path)
1173 att->filename = m_strdup(path);
1175 /* Attempt to determine the appropriate content-type based on the filename
1178 mutt_lookup_mime_type (att, path);
1180 if ((info = mutt_get_content_info (path, att)) == NULL) {
1181 body_list_wipe(&att);
1185 if (!att->subtype) {
1186 if (info->lobin == 0
1187 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1189 * Statistically speaking, there should be more than 10% "lobin"
1190 * chars if this is really a binary file...
1192 att->type = TYPETEXT;
1193 att->subtype = m_strdup("plain");
1195 att->type = TYPEAPPLICATION;
1196 att->subtype = m_strdup("octet-stream");
1200 mutt_update_encoding (att);
1204 static int get_toplevel_encoding (BODY * a)
1208 for (; a; a = a->next) {
1209 if (a->encoding == ENCBINARY)
1212 if (a->encoding == ENC8BIT)
1219 BODY *mutt_make_multipart (BODY * b)
1224 new->type = TYPEMULTIPART;
1225 new->subtype = m_strdup("mixed");
1226 new->encoding = get_toplevel_encoding (b);
1227 parameter_set_boundary(&new->parameter);
1229 new->disposition = DISPINLINE;
1235 /* remove the multipart body if it exists */
1236 BODY *mutt_remove_multipart (BODY * b)
1249 char *mutt_make_date (char *s, ssize_t len)
1251 time_t t = time (NULL);
1252 struct tm *l = localtime (&t);
1253 time_t tz = mutt_local_tz (t);
1257 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1258 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1259 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1260 (int) tz / 60, (int) abs (tz) % 60);
1264 /* wrapper around mutt_write_address() so we can handle very large
1265 recipient lists without needing a huge temporary buffer in memory */
1267 mutt_write_address_list(address_t *addr, FILE *fp, int linelen, int display)
1272 char buf[LONG_STRING];
1273 int len = rfc822_addrcpy(buf, ssizeof(buf), addr, display);
1276 if (linelen + len > 74) {
1278 linelen = 8; /* tab is usually about 8 spaces... */
1280 if (addr->mailbox) {
1290 if (!addr->group && addr->next && addr->next->mailbox) {
1300 /* need to write the list in reverse because they are stored in reverse order
1301 * when parsed to speed up threading
1303 void mutt_write_references(string_list_t *r, FILE *f)
1305 string_list_t *refs[10];
1308 p_clear(refs, countof(refs));
1309 for (i = 0; i < countof(refs) && r; r = r->next) {
1314 fprintf(f, " %s", refs[i]->data);
1318 static int edit_header(int mode, const char *s)
1321 int slen = m_strlen(s);
1323 if (mode != 1 || option(OPTXMAILTO))
1326 p = skipspaces(EditorHeaders);
1328 if (!ascii_strncasecmp(p, s, slen) && p[slen - 1] == ':')
1330 p = skipspaces(p + slen);
1336 /* Note: all RFC2047 encoding should be done outside of this routine, except
1337 * for the "real name." This will allow this routine to be used more than
1338 * once, if necessary.
1340 * Likewise, all IDN processing should happen outside of this routine.
1342 * mode == 1 => "lite" mode (used for edit_hdrs)
1343 * mode == 0 => normal mode. write full header + MIME headers
1344 * mode == -1 => write just the envelope info (used for postponing messages)
1346 * privacy != 0 => will omit any headers which may identify the user.
1347 * Output generated is suitable for being sent through
1348 * anonymous remailer chains.
1351 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1352 int mode, int privacy)
1354 char buffer[LONG_STRING];
1356 string_list_t *tmp = env->userhdrs;
1357 int has_agent = 0; /* user defined user-agent header field exists */
1360 if (!option (OPTNEWSSEND))
1362 if (mode == 0 && !privacy)
1363 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1365 /* OPTUSEFROM is not consulted here so that we can still write a From:
1366 * field if the user sets it with the `my_hdr' command
1368 if (env->from && !privacy) {
1370 rfc822_addrcat(buffer, sizeof(buffer), env->from, 0);
1371 fprintf (fp, "From: %s\n", buffer);
1376 mutt_write_address_list (env->to, fp, 4, 0);
1380 if (!option (OPTNEWSSEND))
1382 if (edit_header(mode, "To:"))
1383 fputs ("To:\n", fp);
1387 mutt_write_address_list (env->cc, fp, 4, 0);
1391 if (!option (OPTNEWSSEND))
1393 if (edit_header(mode, "Cc:"))
1394 fputs ("Cc:\n", fp);
1397 if (mode != 0 || option (OPTWRITEBCC)) {
1398 fputs ("Bcc: ", fp);
1399 mutt_write_address_list (env->bcc, fp, 5, 0);
1404 if (!option (OPTNEWSSEND))
1406 if (edit_header(mode, "Bcc:"))
1407 fputs ("Bcc:\n", fp);
1410 if (env->newsgroups)
1411 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1412 else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Newsgroups:"))
1413 fputs ("Newsgroups:\n", fp);
1415 if (env->followup_to)
1416 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1417 else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Followup-To:"))
1418 fputs ("Followup-To:\n", fp);
1422 fprintf (fp, "Subject: %s\n", env->subject);
1423 else if (mode == 1 && edit_header(mode, "Subject:"))
1424 fputs ("Subject:\n", fp);
1426 /* save message id if the user has set it */
1427 if (env->message_id && !privacy)
1428 fprintf (fp, "Message-ID: %s\n", env->message_id);
1430 if (env->reply_to) {
1431 fputs ("Reply-To: ", fp);
1432 mutt_write_address_list (env->reply_to, fp, 10, 0);
1434 else if (mode > 0 && edit_header(mode, "Reply-To:"))
1435 fputs ("Reply-To:\n", fp);
1437 if (env->mail_followup_to)
1439 if (!option (OPTNEWSSEND))
1442 fputs ("Mail-Followup-To: ", fp);
1443 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1447 if (env->references) {
1448 fputs ("References:", fp);
1449 mutt_write_references (env->references, fp);
1453 /* Add the MIME headers */
1454 fputs ("MIME-Version: 1.0\n", fp);
1455 mutt_write_mime_header (attach, fp);
1458 if (env->in_reply_to) {
1459 fputs ("In-Reply-To:", fp);
1460 mutt_write_references (env->in_reply_to, fp);
1464 /* Add any user defined headers */
1465 for (; tmp; tmp = tmp->next) {
1466 if ((p = strchr (tmp->data, ':'))) {
1467 p = vskipspaces(p + 1);
1469 continue; /* don't emit empty fields. */
1471 /* check to see if the user has overridden the user-agent field */
1472 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1478 fputs (tmp->data, fp);
1483 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1484 if (mod_core.operating_system) {
1485 fprintf(fp, "User-Agent: %s (%s)\n", mutt_make_version(),
1486 mod_core.operating_system);
1488 fprintf(fp, "User-Agent: %s\n", mutt_make_version());
1492 return (ferror (fp) == 0 ? 0 : -1);
1495 static void encode_headers (string_list_t * h)
1501 for (; h; h = h->next) {
1502 if (!(p = strchr (h->data, ':')))
1506 p = vskipspaces(p + 1);
1512 rfc2047_encode_string (&tmp);
1513 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1515 sprintf (h->data + i, ": %s", NONULL (tmp));
1521 const char *mutt_fqdn(short may_hide_host)
1525 if (mod_core.hostname && mod_core.hostname[0] != '@') {
1526 p = mod_core.hostname;
1528 if (may_hide_host && option (OPTHIDDENHOST)) {
1529 if ((p = strchr(mod_core.hostname, '.')))
1532 /* sanity check: don't hide the host if
1533 the fqdn is something like detebe.org. */
1535 if (!p || !(q = strchr(p, '.')))
1536 p = mod_core.hostname;
1543 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1545 #define APPEND_FMT(fmt, arg) \
1547 int snlen = snprintf(buf, len, fmt, arg); \
1552 #define APPEND_BYTE(c) \
1566 static char MsgIdPfx = 'A';
1570 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1571 APPEND_BYTE((isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.');
1579 APPEND_FMT("%02d", tm->tm_mday);
1582 APPEND_FMT("%02d", tm->tm_hour);
1585 APPEND_FMT("%02d", tm->tm_mon + 1);
1588 APPEND_FMT("%02d", tm->tm_min);
1591 APPEND_FMT("%lo", (unsigned long)now);
1594 APPEND_FMT("%u", (unsigned int)getpid());
1597 APPEND_FMT("%c", MsgIdPfx);
1598 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1601 APPEND_FMT("%u", (unsigned int)rand());
1604 APPEND_FMT("%x", (unsigned int)rand());
1607 APPEND_FMT("%02d", tm->tm_sec);
1610 APPEND_FMT("%u", (unsigned int) now);
1613 APPEND_FMT("%x", (unsigned int) now);
1615 case 'Y': /* this will break in the year 10000 ;-) */
1616 APPEND_FMT("%04d", tm->tm_year + 1900);
1621 default: /* invalid formats are replaced by '.' */
1632 static char *mutt_gen_msgid (void)
1635 char localpart[STRING];
1638 if (!(fqdn = mutt_fqdn(0)))
1639 fqdn = NONULL(mod_core.shorthost);
1641 mutt_gen_localpart(localpart, sizeof(localpart), MsgIdFormat);
1642 snprintf(buf, sizeof(buf), "<%s@%s>", localpart, fqdn);
1643 return m_strdup(buf);
1646 static void alarm_handler (int sig __attribute__ ((unused)))
1651 /* invoke sendmail in a subshell
1652 path (in) path to program to execute
1653 args (in) arguments to pass to program
1654 msg (in) temp file containing message to send
1655 tempfile (out) if sendmail is put in the background, this points
1656 to the temporary file containing the stdout of the
1659 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1665 mutt_block_signals_system ();
1668 /* we also don't want to be stopped right now */
1669 sigaddset (&set, SIGTSTP);
1670 sigprocmask (SIG_BLOCK, &set, NULL);
1672 if (MTransport.sendmail_wait >= 0) {
1673 char tmp[_POSIX_PATH_MAX];
1676 *tempfile = m_strdup(tmp);
1679 if ((pid = fork ()) == 0) {
1680 struct sigaction act, oldalrm;
1682 /* save parent's ID before setsid() */
1685 /* we want the delivery to continue even after the main process dies,
1686 * so we put ourselves into another session right away
1690 /* next we close all open files */
1691 for (fd = 0; fd < getdtablesize(); fd++)
1694 /* now the second fork() */
1695 if ((pid = fork ()) == 0) {
1696 /* "msg" will be opened as stdin */
1697 if (open (msg, O_RDONLY, 0) < 0) {
1703 if (MTransport.sendmail_wait >= 0) {
1704 /* *tempfile will be opened as stdout */
1705 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1708 /* redirect stderr to *tempfile too */
1712 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1714 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1718 execv (path, (char**)args);
1721 else if (pid == -1) {
1727 /* sendmail_wait > 0: interrupt waitpid() after sendmail_wait seconds
1728 * sendmail_wait = 0: wait forever
1729 * sendmail_wait < 0: don't wait
1731 if (MTransport.sendmail_wait > 0) {
1733 act.sa_handler = alarm_handler;
1735 /* need to make sure waitpid() is interrupted on SIGALRM */
1736 act.sa_flags = SA_INTERRUPT;
1740 sigemptyset (&act.sa_mask);
1741 sigaction (SIGALRM, &act, &oldalrm);
1742 alarm (MTransport.sendmail_wait);
1744 else if (MTransport.sendmail_wait < 0)
1745 _exit (0xff & EX_OK);
1747 if (waitpid (pid, &st, 0) > 0) {
1748 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1749 if (MTransport.sendmail_wait && st == (0xff & EX_OK)) {
1750 unlink (*tempfile); /* no longer needed */
1754 st = (MTransport.sendmail_wait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1755 if (MTransport.sendmail_wait > 0) {
1761 /* reset alarm; not really needed, but... */
1763 sigaction (SIGALRM, &oldalrm, NULL);
1765 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1766 /* the parent is already dead */
1774 sigprocmask (SIG_UNBLOCK, &set, NULL);
1776 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1777 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1779 st = S_ERR; /* error */
1781 mutt_unblock_signals_system (1);
1786 static const char **
1787 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1789 for (; addr; addr = addr->next) {
1790 /* weed out group mailboxes, since those are for display only */
1791 if (addr->mailbox && !addr->group) {
1792 if (*argslen == *argsmax)
1793 p_realloc(&args, *argsmax += 5);
1794 args[(*argslen)++] = addr->mailbox;
1800 static const char **
1801 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1803 if (*argslen == *argsmax) {
1804 p_realloc(&args, *argsmax += 5);
1806 args[(*argslen)++] = s;
1810 int mutt_invoke_mta(address_t *from, address_t *to, address_t *cc,
1811 address_t *bcc, const char *msg, int eightbit)
1813 char cmd[LONG_STRING];
1814 char *ps = NULL, *path = NULL, *childout = NULL;
1815 const char **args = NULL;
1816 ssize_t argslen = 0, argsmax = 0;
1820 if (option (OPTNEWSSEND)) {
1827 m_strcpy(cmd, sizeof(cmd), MTransport.sendmail);
1832 while ((ps = strtok(ps, " "))) {
1833 if (argslen == argsmax)
1834 p_realloc(&args, argsmax += 5);
1837 args[argslen++] = ps;
1839 path = m_strdup(ps);
1840 ps = strrchr (ps, '/');
1845 args[argslen++] = ps;
1852 if (!option (OPTNEWSSEND)) {
1854 if (eightbit && MTransport.use_8bitmime)
1855 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1857 if (MTransport.use_envelope_from) {
1858 address_t *f = MTransport.envelope_from_address;
1859 if (!f && from && !from->next)
1862 args = add_option (args, &argslen, &argsmax, "-f");
1863 args = add_args (args, &argslen, &argsmax, f);
1866 if (MTransport.dsn_notify) {
1867 args = add_option (args, &argslen, &argsmax, "-N");
1868 args = add_option (args, &argslen, &argsmax, MTransport.dsn_notify);
1870 if (MTransport.dsn_return) {
1871 args = add_option (args, &argslen, &argsmax, "-R");
1872 args = add_option (args, &argslen, &argsmax, MTransport.dsn_return);
1874 args = add_option (args, &argslen, &argsmax, "--");
1875 args = add_args (args, &argslen, &argsmax, to);
1876 args = add_args (args, &argslen, &argsmax, cc);
1877 args = add_args (args, &argslen, &argsmax, bcc);
1882 if (argslen >= argsmax)
1883 p_realloc(&args, ++argsmax);
1885 args[argslen++] = NULL;
1887 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
1889 mutt_error (_("Error sending message, child exited %d (%s)."), i,
1894 if (!stat(childout, &st) && st.st_size > 0)
1895 mutt_pager(_("Output of the delivery process"), childout, 0, NULL);
1902 p_delete(&childout);
1906 if (i == (EX_OK & 0xff))
1908 else if (i == S_BKG)
1915 /* For postponing (!final) do the necessary encodings only */
1916 void mutt_prepare_envelope (ENVELOPE * env, int final)
1919 if (env->bcc && !(env->to || env->cc)) {
1920 /* some MTA's will put an Apparently-To: header field showing the Bcc:
1921 * recipients if there is no To: or Cc: field, so attempt to suppress
1922 * it by using an empty To: field.
1924 env->to = address_new();
1926 env->to->next = address_new();
1927 env->to->mailbox = m_strdup("undisclosed-recipients");
1930 mutt_set_followup_to(env);
1932 if (!env->message_id && !m_strisempty(MsgIdFormat))
1933 env->message_id = mutt_gen_msgid();
1936 /* Take care of 8-bit => 7-bit conversion. */
1937 rfc2047_encode_adrlist(env->to, "To");
1938 rfc2047_encode_adrlist(env->cc, "Cc");
1939 rfc2047_encode_adrlist(env->bcc, "Bcc");
1940 rfc2047_encode_adrlist(env->from, "From");
1941 rfc2047_encode_adrlist(env->mail_followup_to, "Mail-Followup-To");
1942 rfc2047_encode_adrlist(env->reply_to, "Reply-To");
1945 rfc2047_encode_string (&env->subject);
1946 encode_headers (env->userhdrs);
1949 void mutt_unprepare_envelope (ENVELOPE * env)
1951 string_list_t *item;
1953 for (item = env->userhdrs; item; item = item->next)
1954 rfc2047_decode(&item->data);
1956 address_list_wipe(&env->mail_followup_to);
1958 /* back conversions */
1959 rfc2047_decode_adrlist(env->to);
1960 rfc2047_decode_adrlist(env->cc);
1961 rfc2047_decode_adrlist(env->bcc);
1962 rfc2047_decode_adrlist(env->from);
1963 rfc2047_decode_adrlist(env->reply_to);
1964 rfc2047_decode(&env->subject);
1967 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
1968 const char *resent_from, address_t * env_from)
1972 char date[STRING], tempfile[_POSIX_PATH_MAX];
1973 MESSAGE *msg = NULL;
1976 /* Try to bounce each message out, aborting if we get any failures. */
1977 for (i = 0; i < Context->msgcount; i++)
1978 if (Context->hdrs[i]->tagged)
1980 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
1985 /* If we failed to open a message, return with error */
1986 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
1992 f = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
1994 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
1996 if (!option (OPTBOUNCEDELIVERED))
1997 ch_flags |= CH_WEED_DELIVERED;
1999 fseeko (fp, h->offset, 0);
2000 fprintf (f, "Resent-From: %s", resent_from);
2001 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2002 if (!m_strisempty(MsgIdFormat))
2003 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid());
2004 fputs ("Resent-To: ", f);
2005 mutt_write_address_list (to, f, 11, 0);
2006 mutt_copy_header (fp, h, f, ch_flags, NULL);
2008 mutt_copy_bytes (fp, f, h->content->length);
2011 ret = mutt_invoke_mta(env_from, to, NULL, NULL, tempfile,
2012 h->content->encoding == ENC8BIT);
2016 mx_close_message (&msg);
2021 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2024 char resent_from[STRING];
2028 resent_from[0] = '\0';
2029 from = mutt_default_from ();
2031 rfc822_qualify(from, mutt_fqdn(1));
2033 rfc2047_encode_adrlist(from, "Resent-From");
2034 if (mutt_addrlist_to_idna (from, &err)) {
2035 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2038 rfc822_addrcat(resent_from, sizeof(resent_from), from, 0);
2041 unset_option (OPTNEWSSEND);
2044 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2046 address_list_wipe(&from);
2051 static void set_noconv_flags (BODY * b, short flag)
2053 for (; b; b = b->next) {
2054 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2055 set_noconv_flags (b->parts, flag);
2056 else if (b->type == TYPETEXT && b->noconv) {
2057 parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2062 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2063 int post, char *fcc)
2067 char tempfile[_POSIX_PATH_MAX];
2068 FILE *tempfp = NULL;
2072 set_noconv_flags (hdr->content, 1);
2074 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2078 /* We need to add a Content-Length field to avoid problems where a line in
2079 * the message body begins with "From "
2081 if (f.magic == M_MBOX) {
2082 tempfp = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
2084 mutt_error(_("Could not create temporary file"));
2085 mx_close_mailbox (&f, NULL);
2090 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2091 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2092 mx_close_mailbox (&f, NULL);
2096 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2097 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2099 mutt_write_rfc822_header(msg->fp, hdr->env, hdr->content, -post, 0);
2101 /* (postponment) if this was a reply of some sort, <msgid> contians the
2102 * Message-ID: of message replied to. Save it using a special X-Mutt-
2103 * header so it can be picked up if the message is recalled at a later
2104 * point in time. This will allow the message to be marked as replied if
2105 * the same mailbox is still open.
2108 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2110 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2111 * it can be picked up when the message is recalled
2114 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2115 fprintf (msg->fp, "Status: RO\n");
2117 /* (postponment) if the mail is to be signed or encrypted, save this info */
2118 if (post && (hdr->security & APPLICATION_PGP)) {
2119 fputs ("X-Mutt-PGP: ", msg->fp);
2120 if (hdr->security & ENCRYPT)
2121 fputc ('E', msg->fp);
2122 if (hdr->security & SIGN) {
2123 fputc ('S', msg->fp);
2124 if (PgpSignAs && *PgpSignAs)
2125 fprintf (msg->fp, "<%s>", PgpSignAs);
2127 if (hdr->security & INLINE)
2128 fputc ('I', msg->fp);
2129 fputc ('\n', msg->fp);
2132 /* (postponment) if the mail is to be signed or encrypted, save this info */
2133 if (post && (hdr->security & APPLICATION_SMIME)) {
2134 fputs ("X-Mutt-SMIME: ", msg->fp);
2135 if (hdr->security & ENCRYPT) {
2136 fputc ('E', msg->fp);
2137 if (SmimeCryptAlg && *SmimeCryptAlg)
2138 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2140 if (hdr->security & SIGN) {
2141 fputc ('S', msg->fp);
2142 if (SmimeDefaultKey && *SmimeDefaultKey)
2143 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2145 if (hdr->security & INLINE)
2146 fputc ('I', msg->fp);
2147 fputc ('\n', msg->fp);
2150 /* (postponement) if the mail is to be sent through a mixmaster
2151 * chain, save that information
2153 if (post && hdr->chain && hdr->chain) {
2156 fputs ("X-Mutt-Mix:", msg->fp);
2157 for (p = hdr->chain; p; p = p->next)
2158 fprintf (msg->fp, " %s", (char *) p->data);
2160 fputc ('\n', msg->fp);
2164 char sasha[LONG_STRING];
2167 mutt_write_mime_body (hdr->content, tempfp);
2169 /* make sure the last line ends with a newline. Emacs doesn't ensure
2170 * this will happen, and it can cause problems parsing the mailbox
2173 fseeko (tempfp, -1, 2);
2174 if (fgetc (tempfp) != '\n') {
2175 fseeko (tempfp, 0, 2);
2176 fputc ('\n', tempfp);
2180 if (ferror (tempfp)) {
2183 mx_commit_message (msg, &f); /* XXX - really? */
2184 mx_close_message (&msg);
2185 mx_close_mailbox (&f, NULL);
2189 /* count the number of lines */
2191 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2193 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2194 fprintf (msg->fp, "Lines: %d\n\n", lines);
2196 /* copy the body and clean up */
2198 r = mutt_copy_stream (tempfp, msg->fp);
2199 if (m_fclose(&tempfp) != 0)
2201 /* if there was an error, leave the temp version */
2205 fputc ('\n', msg->fp); /* finish off the header */
2206 r = mutt_write_mime_body (hdr->content, msg->fp);
2209 if (mx_commit_message (msg, &f) != 0)
2211 mx_close_message (&msg);
2212 mx_close_mailbox (&f, NULL);
2215 set_noconv_flags (hdr->content, 0);