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"
32 # include "mutt_libesmtp.h"
33 #endif /* USE_LIBESMTP */
36 #include <nntp/nntp.h>
39 #ifdef HAVE_SYSEXITS_H
41 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
45 static void transform_to_7bit (BODY * a, FILE * fpin);
47 static void encode_quoted (fgetconv_t * fc, FILE * fout, int istext)
50 char line[77], savechar;
52 while ((c = fgetconv (fc)) != EOF) {
53 /* Wrap the line if needed. */
54 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
55 /* If the last character is "quoted", then be sure to move all three
56 * characters to the next line. Otherwise, just move the last
59 if (line[linelen - 3] == '=') {
60 line[linelen - 3] = 0;
65 line[1] = line[linelen - 2];
66 line[2] = line[linelen - 1];
70 savechar = line[linelen - 1];
71 line[linelen - 1] = '=';
80 /* Escape lines that begin with/only contain "the message separator". */
81 if (linelen == 4 && !m_strncmp("From", line, 4)) {
82 m_strcpy(line, sizeof(line), "=46rom");
85 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
86 m_strcpy(line, sizeof(line), "=66rom");
89 else if (linelen == 1 && line[0] == '.') {
90 m_strcpy(line, sizeof(line), "=2E");
95 if (c == '\n' && istext) {
96 /* Check to make sure there is no trailing space on this line. */
98 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
100 sprintf (line + linelen - 1, "=%2.2X",
101 (unsigned char) line[linelen - 1]);
105 savechar = line[linelen - 1];
107 line[linelen - 1] = '=';
110 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
120 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
121 /* Check to make sure there is enough room for the quoted character.
122 * If not, wrap to the next line.
125 line[linelen++] = '=';
131 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
135 /* Don't worry about wrapping the line here. That will happen during
136 * the next iteration when I'll also know what the next character is.
142 /* Take care of anything left in the buffer */
144 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
145 /* take care of trailing whitespace */
147 sprintf (line + linelen - 1, "=%2.2X",
148 (unsigned char) line[linelen - 1]);
150 savechar = line[linelen - 1];
151 line[linelen - 1] = '=';
155 sprintf (line, "=%2.2X", (unsigned char) savechar);
164 static char b64_buffer[3];
165 static short b64_num;
166 static short b64_linelen;
168 static void b64_flush (FILE * fout)
175 if (b64_linelen >= 72) {
180 for (i = b64_num; i < 3; i++)
181 b64_buffer[i] = '\0';
183 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
186 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
191 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
195 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
200 while (b64_linelen % 4) {
209 static void b64_putc (char c, FILE * fout)
214 b64_buffer[b64_num++] = c;
218 static void encode_base64 (fgetconv_t * fc, FILE * fout, int istext)
222 b64_num = b64_linelen = 0;
224 while ((ch = fgetconv (fc)) != EOF) {
225 if (istext && ch == '\n' && ch1 != '\r')
226 b64_putc ('\r', fout);
234 static void encode_8bit (fgetconv_t * fc, FILE * fout,
235 int istext __attribute__ ((unused)))
239 while ((ch = fgetconv (fc)) != EOF)
244 int mutt_write_mime_header (BODY * a, FILE * f)
253 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
258 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
260 for (p = a->parameter; p; p = p->next) {
269 tmp = m_strdup(p->value);
270 encode = rfc2231_encode_string (&tmp);
271 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
273 /* Dirty hack to make messages readable by Outlook Express
274 * for the Mac: force quotes around the boundary parameter
275 * even when they aren't needed.
278 if (!ascii_strcasecmp (p->attribute, "boundary")
279 && !strcmp (buffer, tmp))
280 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
284 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
286 if (len + tmplen + 2 > 76) {
295 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
303 fprintf (f, "Content-Description: %s\n", a->description);
305 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
306 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
309 if (!(fn = a->d_filename))
315 /* Strip off the leading path... */
316 if ((t = strrchr (fn, '/')))
323 encode = rfc2231_encode_string (&tmp);
324 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
326 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
332 if (a->encoding != ENC7BIT)
333 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
335 /* Do NOT add the terminator here!!! */
336 return (ferror (f) ? -1 : 0);
339 int mutt_write_mime_body (BODY * a, FILE * f)
342 char boundary[STRING];
343 char send_charset[STRING];
348 if (a->type == TYPEMULTIPART) {
349 /* First, find the boundary to use */
350 if (!(p = parameter_getval(a->parameter, "boundary"))) {
351 mutt_error _("No boundary parameter found! [report this error]");
355 m_strcpy(boundary, sizeof(boundary), p);
357 for (t = a->parts; t; t = t->next) {
358 fprintf (f, "\n--%s\n", boundary);
359 if (mutt_write_mime_header (t, f) == -1)
362 if (mutt_write_mime_body (t, f) == -1)
365 fprintf (f, "\n--%s--\n", boundary);
366 return (ferror (f) ? -1 : 0);
369 /* This is pretty gross, but it's the best solution for now... */
370 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
371 fputs ("Version: 1\n", f);
375 if ((fpin = fopen (a->filename, "r")) == NULL) {
376 mutt_error (_("%s no longer exists!"), a->filename);
380 if (a->type == TYPETEXT && (!a->noconv))
381 fc = fgetconv_open (fpin, a->file_charset,
382 mutt_get_body_charset (send_charset,
383 sizeof (send_charset), a), 0);
385 fc = fgetconv_open (fpin, 0, 0, 0);
387 #define write_as_text_part(a) (mutt_is_text_part(a) || mutt_is_application_pgp(a))
388 if (a->encoding == ENCQUOTEDPRINTABLE)
389 encode_quoted (fc, f, write_as_text_part (a));
390 else if (a->encoding == ENCBASE64)
391 encode_base64 (fc, f, write_as_text_part (a));
392 else if (a->type == TYPETEXT && (!a->noconv))
393 encode_8bit (fc, f, write_as_text_part (a));
395 mutt_copy_stream (fpin, f);
396 #undef write_as_text_part
398 fgetconv_close (&fc);
401 return (ferror (f) ? -1 : 0);
413 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
417 int whitespace = s->whitespace;
419 int linelen = s->linelen;
420 int was_cr = s->was_cr;
422 if (!d) { /* This signals EOF */
425 if (linelen > info->linemax)
426 info->linemax = linelen;
431 for (; dlen; d++, dlen--) {
444 if (linelen > info->linemax)
445 info->linemax = linelen;
460 if (linelen > info->linemax)
461 info->linemax = linelen;
466 else if (ch == '\r') {
474 else if (ch == '\t' || ch == '\f') {
478 else if (ch < 32 || ch == 127)
482 if ((ch == 'F') || (ch == 'f'))
492 if (linelen == 2 && ch != 'r')
494 else if (linelen == 3 && ch != 'o')
496 else if (linelen == 4) {
509 if (ch != ' ' && ch != '\t')
514 s->whitespace = whitespace;
516 s->linelen = linelen;
522 * Find the best charset conversion of the file from fromcode into one
523 * of the tocodes. If successful, set *tocode and CONTENT *info and
524 * return the number of characters converted inexactly. If no
525 * conversion was possible, return -1.
527 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
528 * which would otherwise prevent us from knowing the number of inexact
529 * conversions. Where the candidate target charset is UTF-8 we avoid
530 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
531 * fails with some libraries.
533 * We assume that the output from iconv is never more than 4 times as
534 * long as the input for any pair of charsets we might be interested
537 static ssize_t convert_file_to (FILE * file, const char *fromcode,
538 int ncodes, const char **tocodes,
539 int *tocode, CONTENT * info)
543 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
546 ssize_t ibl, obl, ubl, ubl1, n, ret;
549 CONTENT_STATE *states;
552 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
553 if (cd1 == MUTT_ICONV_ERROR)
556 cd = p_new(iconv_t, ncodes);
557 score = p_new(ssize_t, ncodes);
558 states = p_new(CONTENT_STATE, ncodes);
559 infos = p_new(CONTENT, ncodes);
561 for (i = 0; i < ncodes; i++)
562 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
563 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
565 /* Special case for conversion to UTF-8 */
566 cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
572 /* Try to fill input buffer */
573 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
576 /* Convert to UTF-8 */
578 ob = bufu, obl = sizeof (bufu);
579 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
580 if (n == -1 && ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
586 /* Convert from UTF-8 */
587 for (i = 0; i < ncodes; i++)
588 if (cd[i] != MUTT_ICONV_ERROR && score[i] != -1) {
589 ub = bufu, ubl = ubl1;
590 ob = bufo, obl = sizeof (bufo);
591 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
597 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
600 else if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1)
601 /* Special case for conversion to UTF-8 */
602 update_content_info (&infos[i], &states[i], bufu, ubl1);
605 /* Save unused input */
606 memmove (bufi, ib, ibl);
607 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
614 /* Find best score */
616 for (i = 0; i < ncodes; i++) {
617 if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1) {
618 /* Special case for conversion to UTF-8 */
623 else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
625 else if (ret == -1 || score[i] < ret) {
633 memcpy (info, &infos[*tocode], sizeof (CONTENT));
634 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
638 for (i = 0; i < ncodes; i++)
639 if (cd[i] != MUTT_ICONV_ERROR)
651 #endif /* !HAVE_ICONV */
655 * Find the first of the fromcodes that gives a valid conversion and
656 * the best charset conversion of the file into one of the tocodes. If
657 * successful, set *fromcode and *tocode to dynamically allocated
658 * strings, set CONTENT *info, and return the number of characters
659 * converted inexactly. If no conversion was possible, return -1.
661 * Both fromcodes and tocodes may be colon-separated lists of charsets.
662 * However, if fromcode is zero then fromcodes is assumed to be the
663 * name of a single charset even if it contains a colon.
665 static ssize_t convert_file_from_to (FILE * file,
666 const char *fromcodes,
667 const char *tocodes, char **fromcode,
668 char **tocode, CONTENT * info)
676 /* Count the tocodes */
678 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
679 if ((c1 = strchr (c, ':')) == c)
685 tcode = p_new(char *, ncodes);
686 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
687 if ((c1 = strchr (c, ':')) == c)
689 tcode[i] = m_substrdup(c, c1);
694 /* Try each fromcode in turn */
695 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
696 if ((c1 = strchr (c, ':')) == c)
698 fcode = m_substrdup(c, c1);
700 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
712 /* There is only one fromcode */
713 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
722 for (i = 0; i < ncodes; i++)
731 * Analyze the contents of a file to determine which MIME encoding to use.
732 * Also set the body charset, sometimes, or not.
734 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
739 char *fromcode = NULL;
750 if (stat (fname, &sb) == -1) {
751 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
755 if (!S_ISREG (sb.st_mode)) {
756 mutt_error (_("%s isn't a regular file."), fname);
760 if ((fp = fopen (fname, "r")) == NULL) {
764 info = p_new(CONTENT, 1);
767 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
768 const char *chs = parameter_getval(b->parameter, "charset");
769 char *fchs = b->use_disp && !m_strisempty(MCharset.file_charset)
770 ? FileCharset : MCharset.charset;
771 if (MCharset.charset && (chs || MCharset.send_charset) &&
772 convert_file_from_to (fp, fchs, chs ? chs : MCharset.send_charset,
773 &fromcode, &tocode, info) != -1) {
775 charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
776 parameter_setval(&b->parameter, "charset", chsbuf);
778 b->file_charset = fromcode;
786 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
787 update_content_info (info, &state, buffer, r);
788 update_content_info (info, &state, 0, 0);
792 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
793 parameter_setval(&b->parameter, "charset",
794 (!info->hibin ? "us-ascii"
795 : MCharset.charset && !charset_is_us_ascii(MCharset.charset)
796 ? MCharset.charset : "unknown-8bit"));
801 /* Given a file with path ``s'', see if there is a registered MIME type.
802 * returns the major MIME type, and copies the subtype to ``d''. First look
803 * for ~/.mime.types, then look in a system mime.types if we can find one.
804 * The longest match is used so that we can match `ps.gz' when `gz' also
808 int mutt_lookup_mime_type (BODY * att, const char *path)
812 char buf[LONG_STRING];
813 char subtype[STRING], xtype[STRING];
815 int szf, sze, cur_sze;
823 szf = m_strlen(path);
825 for (count = 0; count < 4; count++) {
827 * can't use strtok() because we use it in an inner loop below, so use
828 * a switch statement here instead.
832 snprintf(buf, sizeof (buf), "%s/.mime.types", NONULL(MCore.homedir));
835 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
838 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
841 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
844 goto bye; /* shouldn't happen */
847 if ((f = fopen (buf, "r")) != NULL) {
848 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
849 /* weed out any comments */
850 if ((p = strchr (buf, '#')))
853 /* remove any leading space. */
854 ct = vskipspaces(buf);
856 /* position on the next field in this line */
857 if ((p = strpbrk (ct, " \t")) == NULL)
862 /* cycle through the file extensions */
863 while ((p = strtok (p, " \t\n"))) {
865 if ((sze > cur_sze) && (szf >= sze) &&
866 (m_strcasecmp(path + szf - sze, p) == 0
867 || ascii_strcasecmp (path + szf - sze, p) == 0)
868 && (szf == sze || path[szf - sze - 1] == '.'))
870 /* get the content-type */
872 if ((p = strchr (ct, '/')) == NULL) {
873 /* malformed line, just skip it. */
878 for (q = p; *q && !ISSPACE (*q); q++);
880 m_strncpy(subtype, sizeof(subtype), p, q - p);
882 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
883 m_strcpy(xtype, sizeof(xtype), ct);
896 if (type != TYPEOTHER || *xtype != '\0') {
898 m_strreplace(&att->subtype, subtype);
899 m_strreplace(&att->xtype, xtype);
905 void mutt_message_to_7bit (BODY * a, FILE * fp)
907 char temp[_POSIX_PATH_MAX];
913 if (!a->filename && fp)
915 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
916 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
921 if (stat (a->filename, &sb) == -1) {
922 mutt_perror ("stat");
925 a->length = sb.st_size;
928 fpout = m_tempfile(temp, sizeof(temp), NONULL(MCore.tmpdir), NULL);
930 mutt_error(_("Could not create temporary file"));
934 fseeko (fpin, a->offset, 0);
935 a->parts = mutt_parse_messageRFC822 (fpin, a);
937 transform_to_7bit (a->parts, fpin);
939 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
940 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
942 fputs ("MIME-Version: 1.0\n", fpout);
943 mutt_write_mime_header (a->parts, fpout);
945 mutt_write_mime_body (a->parts, fpout);
957 a->encoding = ENC7BIT;
958 a->d_filename = a->filename;
959 if (a->filename && a->unlink)
960 unlink (a->filename);
961 a->filename = m_strdup(temp);
963 if (stat (a->filename, &sb) == -1) {
964 mutt_perror ("stat");
967 a->length = sb.st_size;
968 body_list_wipe(&a->parts);
969 a->hdr->content = NULL;
972 static void transform_to_7bit (BODY * a, FILE * fpin)
974 char buff[_POSIX_PATH_MAX];
979 for (; a; a = a->next) {
980 if (a->type == TYPEMULTIPART) {
981 if (a->encoding != ENC7BIT)
982 a->encoding = ENC7BIT;
984 transform_to_7bit (a->parts, fpin);
986 else if (mutt_is_message_type(a)) {
987 mutt_message_to_7bit (a, fpin);
991 a->force_charset = 1;
993 s.fpout = m_tempfile(buff, sizeof(buff), NONULL(MCore.tmpdir), NULL);
995 mutt_error(_("Could not create temporary file"));
999 mutt_decode_attachment (a, &s);
1001 a->d_filename = a->filename;
1002 a->filename = m_strdup(buff);
1004 if (stat (a->filename, &sb) == -1) {
1005 mutt_perror ("stat");
1008 a->length = sb.st_size;
1010 mutt_update_encoding (a);
1011 if (a->encoding == ENC8BIT)
1012 a->encoding = ENCQUOTEDPRINTABLE;
1013 else if (a->encoding == ENCBINARY)
1014 a->encoding = ENCBASE64;
1019 /* determine which Content-Transfer-Encoding to use */
1020 static void mutt_set_encoding (BODY * b, CONTENT * info)
1022 char send_charset[STRING];
1024 if (b->type == TYPETEXT) {
1026 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1027 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1028 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1029 b->encoding = ENCQUOTEDPRINTABLE;
1030 else if (info->hibin)
1031 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1033 b->encoding = ENC7BIT;
1035 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1036 if (info->lobin || info->hibin) {
1037 if (option (OPTALLOW8BIT) && !info->lobin)
1038 b->encoding = ENC8BIT;
1040 mutt_message_to_7bit (b, NULL);
1043 b->encoding = ENC7BIT;
1045 else if (b->type == TYPEAPPLICATION
1046 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1047 b->encoding = ENC7BIT;
1050 /* Determine which encoding is smaller */
1051 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1052 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1053 b->encoding = ENCBASE64;
1055 b->encoding = ENCQUOTEDPRINTABLE;
1059 void mutt_stamp_attachment (BODY * a)
1061 a->stamp = time (NULL);
1064 /* Get a body's character set */
1066 char *mutt_get_body_charset(char *d, ssize_t dlen, BODY * b)
1070 if (b && b->type != TYPETEXT)
1073 p = b ? parameter_getval(b->parameter, "charset") : NULL;
1074 charset_canonicalize(d, dlen, p);
1079 /* Assumes called from send mode where BODY->filename points to actual file */
1080 void mutt_update_encoding (BODY * a)
1083 char chsbuff[STRING];
1085 /* override noconv when it's us-ascii */
1086 if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1089 if (!a->force_charset && !a->noconv)
1090 parameter_delval(&a->parameter, "charset");
1092 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1095 mutt_set_encoding (a, info);
1096 mutt_stamp_attachment (a);
1098 p_delete(&a->content);
1103 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1105 char buffer[LONG_STRING];
1108 int cmflags, chflags;
1109 int pgp = hdr->security;
1111 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1112 (hdr->security & ENCRYPT)) {
1113 if (!crypt_valid_passphrase (hdr->security))
1117 fp = m_tempfile(buffer, sizeof(buffer), NONULL(MCore.tmpdir), NULL);
1122 body->type = TYPEMESSAGE;
1123 body->subtype = m_strdup("rfc822");
1124 body->filename = m_strdup(buffer);
1127 body->disposition = DISPINLINE;
1130 mutt_parse_mime_message (ctx, hdr);
1135 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1136 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1137 chflags |= CH_MIME | CH_TXTPLAIN;
1138 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1139 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1141 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1142 if (mutt_is_multipart_encrypted (hdr->content)) {
1143 chflags |= CH_MIME | CH_NONEWLINE;
1144 cmflags = M_CM_DECODE_PGP;
1147 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1148 chflags |= CH_MIME | CH_TXTPLAIN;
1149 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1152 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1153 chflags |= CH_MIME | CH_TXTPLAIN;
1154 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1155 pgp &= ~SMIMEENCRYPT;
1159 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1164 body->hdr = header_new();
1165 body->hdr->offset = 0;
1166 /* we don't need the user headers here */
1167 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1168 body->hdr->security = pgp;
1169 mutt_update_encoding (body);
1170 body->parts = body->hdr->content;
1177 BODY *mutt_make_file_attach (const char *path)
1183 att->filename = m_strdup(path);
1185 /* Attempt to determine the appropriate content-type based on the filename
1188 mutt_lookup_mime_type (att, path);
1190 if ((info = mutt_get_content_info (path, att)) == NULL) {
1191 body_list_wipe(&att);
1195 if (!att->subtype) {
1196 if (info->lobin == 0
1197 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1199 * Statistically speaking, there should be more than 10% "lobin"
1200 * chars if this is really a binary file...
1202 att->type = TYPETEXT;
1203 att->subtype = m_strdup("plain");
1205 att->type = TYPEAPPLICATION;
1206 att->subtype = m_strdup("octet-stream");
1210 mutt_update_encoding (att);
1214 static int get_toplevel_encoding (BODY * a)
1218 for (; a; a = a->next) {
1219 if (a->encoding == ENCBINARY)
1222 if (a->encoding == ENC8BIT)
1229 BODY *mutt_make_multipart (BODY * b)
1234 new->type = TYPEMULTIPART;
1235 new->subtype = m_strdup("mixed");
1236 new->encoding = get_toplevel_encoding (b);
1237 parameter_set_boundary(&new->parameter);
1239 new->disposition = DISPINLINE;
1245 /* remove the multipart body if it exists */
1246 BODY *mutt_remove_multipart (BODY * b)
1259 char *mutt_make_date (char *s, ssize_t len)
1261 time_t t = time (NULL);
1262 struct tm *l = localtime (&t);
1263 time_t tz = mutt_local_tz (t);
1267 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1268 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1269 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1270 (int) tz / 60, (int) abs (tz) % 60);
1274 /* wrapper around mutt_write_address() so we can handle very large
1275 recipient lists without needing a huge temporary buffer in memory */
1277 mutt_write_address_list(address_t *addr, FILE *fp, int linelen, int display)
1282 char buf[LONG_STRING];
1283 int len = rfc822_addrcpy(buf, ssizeof(buf), addr, display);
1286 if (linelen + len > 74) {
1288 linelen = 8; /* tab is usually about 8 spaces... */
1290 if (addr->mailbox) {
1300 if (!addr->group && addr->next && addr->next->mailbox) {
1310 /* need to write the list in reverse because they are stored in reverse order
1311 * when parsed to speed up threading
1313 void mutt_write_references(string_list_t *r, FILE *f)
1315 string_list_t *refs[10];
1318 p_clear(refs, countof(refs));
1319 for (i = 0; i < countof(refs) && r; r = r->next) {
1324 fprintf(f, " %s", refs[i]->data);
1328 static int edit_header(int mode, const char *s)
1331 int slen = m_strlen(s);
1333 if (mode != 1 || option(OPTXMAILTO))
1336 p = skipspaces(EditorHeaders);
1338 if (!ascii_strncasecmp(p, s, slen) && p[slen - 1] == ':')
1340 p = skipspaces(p + slen);
1346 /* Note: all RFC2047 encoding should be done outside of this routine, except
1347 * for the "real name." This will allow this routine to be used more than
1348 * once, if necessary.
1350 * Likewise, all IDN processing should happen outside of this routine.
1352 * mode == 1 => "lite" mode (used for edit_hdrs)
1353 * mode == 0 => normal mode. write full header + MIME headers
1354 * mode == -1 => write just the envelope info (used for postponing messages)
1356 * privacy != 0 => will omit any headers which may identify the user.
1357 * Output generated is suitable for being sent through
1358 * anonymous remailer chains.
1361 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1362 int mode, int privacy)
1364 char buffer[LONG_STRING];
1366 string_list_t *tmp = env->userhdrs;
1367 int has_agent = 0; /* user defined user-agent header field exists */
1370 if (!option (OPTNEWSSEND))
1372 if (mode == 0 && !privacy)
1373 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1375 /* OPTUSEFROM is not consulted here so that we can still write a From:
1376 * field if the user sets it with the `my_hdr' command
1378 if (env->from && !privacy) {
1380 rfc822_addrcat(buffer, sizeof(buffer), env->from, 0);
1381 fprintf (fp, "From: %s\n", buffer);
1386 mutt_write_address_list (env->to, fp, 4, 0);
1390 if (!option (OPTNEWSSEND))
1392 if (edit_header(mode, "To:"))
1393 fputs ("To:\n", fp);
1397 mutt_write_address_list (env->cc, fp, 4, 0);
1401 if (!option (OPTNEWSSEND))
1403 if (edit_header(mode, "Cc:"))
1404 fputs ("Cc:\n", fp);
1407 if (mode != 0 || option (OPTWRITEBCC)) {
1408 fputs ("Bcc: ", fp);
1409 mutt_write_address_list (env->bcc, fp, 5, 0);
1414 if (!option (OPTNEWSSEND))
1416 if (edit_header(mode, "Bcc:"))
1417 fputs ("Bcc:\n", fp);
1420 if (env->newsgroups)
1421 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1422 else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Newsgroups:"))
1423 fputs ("Newsgroups:\n", fp);
1425 if (env->followup_to)
1426 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1427 else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Followup-To:"))
1428 fputs ("Followup-To:\n", fp);
1430 if (env->x_comment_to)
1431 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1432 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1433 edit_header(mode, "X-Comment-To:"))
1434 fputs ("X-Comment-To:\n", fp);
1438 fprintf (fp, "Subject: %s\n", env->subject);
1439 else if (mode == 1 && edit_header(mode, "Subject:"))
1440 fputs ("Subject:\n", fp);
1442 /* save message id if the user has set it */
1443 if (env->message_id && !privacy)
1444 fprintf (fp, "Message-ID: %s\n", env->message_id);
1446 if (env->reply_to) {
1447 fputs ("Reply-To: ", fp);
1448 mutt_write_address_list (env->reply_to, fp, 10, 0);
1450 else if (mode > 0 && edit_header(mode, "Reply-To:"))
1451 fputs ("Reply-To:\n", fp);
1453 if (env->mail_followup_to)
1455 if (!option (OPTNEWSSEND))
1458 fputs ("Mail-Followup-To: ", fp);
1459 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1463 if (env->references) {
1464 fputs ("References:", fp);
1465 mutt_write_references (env->references, fp);
1469 /* Add the MIME headers */
1470 fputs ("MIME-Version: 1.0\n", fp);
1471 mutt_write_mime_header (attach, fp);
1474 if (env->in_reply_to) {
1475 fputs ("In-Reply-To:", fp);
1476 mutt_write_references (env->in_reply_to, fp);
1480 /* Add any user defined headers */
1481 for (; tmp; tmp = tmp->next) {
1482 if ((p = strchr (tmp->data, ':'))) {
1483 p = vskipspaces(p + 1);
1485 continue; /* don't emit empty fields. */
1487 /* check to see if the user has overridden the user-agent field */
1488 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1494 fputs (tmp->data, fp);
1499 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1500 if (MCore.operating_system) {
1501 fprintf(fp, "User-Agent: %s (%s)\n", mutt_make_version(),
1502 MCore.operating_system);
1504 fprintf(fp, "User-Agent: %s\n", mutt_make_version());
1508 return (ferror (fp) == 0 ? 0 : -1);
1511 static void encode_headers (string_list_t * h)
1517 for (; h; h = h->next) {
1518 if (!(p = strchr (h->data, ':')))
1522 p = vskipspaces(p + 1);
1528 rfc2047_encode_string (&tmp);
1529 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1531 sprintf (h->data + i, ": %s", NONULL (tmp));
1537 const char *mutt_fqdn(short may_hide_host)
1541 if (MCore.hostname && MCore.hostname[0] != '@') {
1544 if (may_hide_host && option (OPTHIDDENHOST)) {
1545 if ((p = strchr(MCore.hostname, '.')))
1548 /* sanity check: don't hide the host if
1549 the fqdn is something like detebe.org. */
1551 if (!p || !(q = strchr(p, '.')))
1559 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1561 #define APPEND_FMT(fmt, arg) \
1563 int snlen = snprintf(buf, len, fmt, arg); \
1568 #define APPEND_BYTE(c) \
1582 static char MsgIdPfx = 'A';
1586 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1587 APPEND_BYTE((isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.');
1595 APPEND_FMT("%02d", tm->tm_mday);
1598 APPEND_FMT("%02d", tm->tm_hour);
1601 APPEND_FMT("%02d", tm->tm_mon + 1);
1604 APPEND_FMT("%02d", tm->tm_min);
1607 APPEND_FMT("%lo", (unsigned long)now);
1610 APPEND_FMT("%u", (unsigned int)getpid());
1613 APPEND_FMT("%c", MsgIdPfx);
1614 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1617 APPEND_FMT("%u", (unsigned int)rand());
1620 APPEND_FMT("%x", (unsigned int)rand());
1623 APPEND_FMT("%02d", tm->tm_sec);
1626 APPEND_FMT("%u", (unsigned int) now);
1629 APPEND_FMT("%x", (unsigned int) now);
1631 case 'Y': /* this will break in the year 10000 ;-) */
1632 APPEND_FMT("%04d", tm->tm_year + 1900);
1637 default: /* invalid formats are replaced by '.' */
1648 static char *mutt_gen_msgid (void)
1651 char localpart[STRING];
1654 if (!(fqdn = mutt_fqdn(0)))
1655 fqdn = NONULL(MCore.shorthost);
1657 mutt_gen_localpart(localpart, sizeof(localpart), MsgIdFormat);
1658 snprintf(buf, sizeof(buf), "<%s@%s>", localpart, fqdn);
1659 return m_strdup(buf);
1662 static RETSIGTYPE alarm_handler (int sig __attribute__ ((unused)))
1667 /* invoke sendmail in a subshell
1668 path (in) path to program to execute
1669 args (in) arguments to pass to program
1670 msg (in) temp file containing message to send
1671 tempfile (out) if sendmail is put in the background, this points
1672 to the temporary file containing the stdout of the
1675 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1681 mutt_block_signals_system ();
1684 /* we also don't want to be stopped right now */
1685 sigaddset (&set, SIGTSTP);
1686 sigprocmask (SIG_BLOCK, &set, NULL);
1688 if (MTransport.sendmail_wait >= 0) {
1689 char tmp[_POSIX_PATH_MAX];
1692 *tempfile = m_strdup(tmp);
1695 if ((pid = fork ()) == 0) {
1696 struct sigaction act, oldalrm;
1698 /* save parent's ID before setsid() */
1701 /* we want the delivery to continue even after the main process dies,
1702 * so we put ourselves into another session right away
1706 /* next we close all open files */
1707 for (fd = 0; fd < getdtablesize(); fd++)
1710 /* now the second fork() */
1711 if ((pid = fork ()) == 0) {
1712 /* "msg" will be opened as stdin */
1713 if (open (msg, O_RDONLY, 0) < 0) {
1719 if (MTransport.sendmail_wait >= 0) {
1720 /* *tempfile will be opened as stdout */
1721 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1724 /* redirect stderr to *tempfile too */
1728 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1730 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1734 execv (path, (char**)args);
1737 else if (pid == -1) {
1743 /* sendmail_wait > 0: interrupt waitpid() after sendmail_wait seconds
1744 * sendmail_wait = 0: wait forever
1745 * sendmail_wait < 0: don't wait
1747 if (MTransport.sendmail_wait > 0) {
1749 act.sa_handler = alarm_handler;
1751 /* need to make sure waitpid() is interrupted on SIGALRM */
1752 act.sa_flags = SA_INTERRUPT;
1756 sigemptyset (&act.sa_mask);
1757 sigaction (SIGALRM, &act, &oldalrm);
1758 alarm (MTransport.sendmail_wait);
1760 else if (MTransport.sendmail_wait < 0)
1761 _exit (0xff & EX_OK);
1763 if (waitpid (pid, &st, 0) > 0) {
1764 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1765 if (MTransport.sendmail_wait && st == (0xff & EX_OK)) {
1766 unlink (*tempfile); /* no longer needed */
1770 st = (MTransport.sendmail_wait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1771 if (MTransport.sendmail_wait > 0) {
1777 /* reset alarm; not really needed, but... */
1779 sigaction (SIGALRM, &oldalrm, NULL);
1781 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1782 /* the parent is already dead */
1790 sigprocmask (SIG_UNBLOCK, &set, NULL);
1792 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1793 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1795 st = S_ERR; /* error */
1797 mutt_unblock_signals_system (1);
1802 static const char **
1803 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1805 for (; addr; addr = addr->next) {
1806 /* weed out group mailboxes, since those are for display only */
1807 if (addr->mailbox && !addr->group) {
1808 if (*argslen == *argsmax)
1809 p_realloc(&args, *argsmax += 5);
1810 args[(*argslen)++] = addr->mailbox;
1816 static const char **
1817 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1819 if (*argslen == *argsmax) {
1820 p_realloc(&args, *argsmax += 5);
1822 args[(*argslen)++] = s;
1826 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1827 address_t * to, address_t * cc, address_t * bcc, /* recips */
1828 const char *msg, /* file containing message */
1830 { /* message contains 8bit chars */
1831 char cmd[LONG_STRING];
1832 char *ps = NULL, *path = NULL, *childout = NULL;
1833 const char **args = NULL;
1834 ssize_t argslen = 0, argsmax = 0;
1838 if (option (OPTNEWSSEND)) {
1839 m_strformat(cmd, sizeof(cmd), 0, Inews, nntp_format_str, 0, 0);
1840 if (m_strisempty(cmd)) {
1841 i = nntp_post (msg);
1848 m_strcpy(cmd, sizeof(cmd), MTransport.sendmail);
1853 while ((ps = strtok(ps, " "))) {
1854 if (argslen == argsmax)
1855 p_realloc(&args, argsmax += 5);
1858 args[argslen++] = ps;
1860 path = m_strdup(ps);
1861 ps = strrchr (ps, '/');
1866 args[argslen++] = ps;
1873 if (!option (OPTNEWSSEND)) {
1875 if (eightbit && MTransport.use_8bitmime)
1876 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1878 if (MTransport.use_envelope_from) {
1879 address_t *f = MTransport.envelope_from_address;
1880 if (!f && from && !from->next)
1883 args = add_option (args, &argslen, &argsmax, "-f");
1884 args = add_args (args, &argslen, &argsmax, f);
1887 if (MTransport.dsn_notify) {
1888 args = add_option (args, &argslen, &argsmax, "-N");
1889 args = add_option (args, &argslen, &argsmax, MTransport.dsn_notify);
1891 if (MTransport.dsn_return) {
1892 args = add_option (args, &argslen, &argsmax, "-R");
1893 args = add_option (args, &argslen, &argsmax, MTransport.dsn_return);
1895 args = add_option (args, &argslen, &argsmax, "--");
1896 args = add_args (args, &argslen, &argsmax, to);
1897 args = add_args (args, &argslen, &argsmax, cc);
1898 args = add_args (args, &argslen, &argsmax, bcc);
1903 if (argslen >= argsmax)
1904 p_realloc(&args, ++argsmax);
1906 args[argslen++] = NULL;
1908 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
1910 mutt_error (_("Error sending message, child exited %d (%s)."), i,
1915 if (!stat(childout, &st) && st.st_size > 0)
1916 mutt_do_pager(_("Output of the delivery process"), childout, 0,
1924 p_delete(&childout);
1928 if (i == (EX_OK & 0xff))
1930 else if (i == S_BKG)
1937 int mutt_invoke_mta (address_t * from, /* the sender */
1938 address_t * to, address_t * cc, address_t * bcc, /* recips */
1939 const char *msg, /* file containing message */
1941 { /* message contains 8bit chars */
1944 if (!option (OPTNEWSSEND))
1947 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
1950 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
1953 /* For postponing (!final) do the necessary encodings only */
1954 void mutt_prepare_envelope (ENVELOPE * env, int final)
1957 if (env->bcc && !(env->to || env->cc)) {
1958 /* some MTA's will put an Apparently-To: header field showing the Bcc:
1959 * recipients if there is no To: or Cc: field, so attempt to suppress
1960 * it by using an empty To: field.
1962 env->to = address_new();
1964 env->to->next = address_new();
1965 env->to->mailbox = m_strdup("undisclosed-recipients");
1968 mutt_set_followup_to(env);
1970 if (!env->message_id && !m_strisempty(MsgIdFormat))
1971 env->message_id = mutt_gen_msgid();
1974 /* Take care of 8-bit => 7-bit conversion. */
1975 rfc2047_encode_adrlist(env->to, "To");
1976 rfc2047_encode_adrlist(env->cc, "Cc");
1977 rfc2047_encode_adrlist(env->bcc, "Bcc");
1978 rfc2047_encode_adrlist(env->from, "From");
1979 rfc2047_encode_adrlist(env->mail_followup_to, "Mail-Followup-To");
1980 rfc2047_encode_adrlist(env->reply_to, "Reply-To");
1984 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
1987 rfc2047_encode_string (&env->subject);
1989 encode_headers (env->userhdrs);
1992 void mutt_unprepare_envelope (ENVELOPE * env)
1994 string_list_t *item;
1996 for (item = env->userhdrs; item; item = item->next)
1997 rfc2047_decode(&item->data);
1999 address_list_wipe(&env->mail_followup_to);
2001 /* back conversions */
2002 rfc2047_decode_adrlist(env->to);
2003 rfc2047_decode_adrlist(env->cc);
2004 rfc2047_decode_adrlist(env->bcc);
2005 rfc2047_decode_adrlist(env->from);
2006 rfc2047_decode_adrlist(env->reply_to);
2007 rfc2047_decode(&env->subject);
2010 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2011 const char *resent_from, address_t * env_from)
2015 char date[STRING], tempfile[_POSIX_PATH_MAX];
2016 MESSAGE *msg = NULL;
2019 /* Try to bounce each message out, aborting if we get any failures. */
2020 for (i = 0; i < Context->msgcount; i++)
2021 if (Context->hdrs[i]->tagged)
2023 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2028 /* If we failed to open a message, return with error */
2029 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2035 f = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
2037 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2039 if (!option (OPTBOUNCEDELIVERED))
2040 ch_flags |= CH_WEED_DELIVERED;
2042 fseeko (fp, h->offset, 0);
2043 fprintf (f, "Resent-From: %s", resent_from);
2044 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2045 if (!m_strisempty(MsgIdFormat))
2046 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid());
2047 fputs ("Resent-To: ", f);
2048 mutt_write_address_list (to, f, 11, 0);
2049 mutt_copy_header (fp, h, f, ch_flags, NULL);
2051 mutt_copy_bytes (fp, f, h->content->length);
2054 ret = mutt_invoke_mta(env_from, to, NULL, NULL, tempfile,
2055 h->content->encoding == ENC8BIT);
2059 mx_close_message (&msg);
2064 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2067 char resent_from[STRING];
2071 resent_from[0] = '\0';
2072 from = mutt_default_from ();
2074 rfc822_qualify(from, mutt_fqdn(1));
2076 rfc2047_encode_adrlist(from, "Resent-From");
2077 if (mutt_addrlist_to_idna (from, &err)) {
2078 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2081 rfc822_addrcat(resent_from, sizeof(resent_from), from, 0);
2084 unset_option (OPTNEWSSEND);
2087 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2089 address_list_wipe(&from);
2094 static void set_noconv_flags (BODY * b, short flag)
2096 for (; b; b = b->next) {
2097 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2098 set_noconv_flags (b->parts, flag);
2099 else if (b->type == TYPETEXT && b->noconv) {
2100 parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2105 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2106 int post, char *fcc)
2110 char tempfile[_POSIX_PATH_MAX];
2111 FILE *tempfp = NULL;
2115 set_noconv_flags (hdr->content, 1);
2117 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2121 /* We need to add a Content-Length field to avoid problems where a line in
2122 * the message body begins with "From "
2124 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2125 tempfp = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
2127 mutt_error(_("Could not create temporary file"));
2128 mx_close_mailbox (&f, NULL);
2133 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2134 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2135 mx_close_mailbox (&f, NULL);
2139 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2140 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2142 mutt_write_rfc822_header(msg->fp, hdr->env, hdr->content, -post, 0);
2144 /* (postponment) if this was a reply of some sort, <msgid> contians the
2145 * Message-ID: of message replied to. Save it using a special X-Mutt-
2146 * header so it can be picked up if the message is recalled at a later
2147 * point in time. This will allow the message to be marked as replied if
2148 * the same mailbox is still open.
2151 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2153 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2154 * it can be picked up when the message is recalled
2157 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2158 fprintf (msg->fp, "Status: RO\n");
2160 /* (postponment) if the mail is to be signed or encrypted, save this info */
2161 if (post && (hdr->security & APPLICATION_PGP)) {
2162 fputs ("X-Mutt-PGP: ", msg->fp);
2163 if (hdr->security & ENCRYPT)
2164 fputc ('E', msg->fp);
2165 if (hdr->security & SIGN) {
2166 fputc ('S', msg->fp);
2167 if (PgpSignAs && *PgpSignAs)
2168 fprintf (msg->fp, "<%s>", PgpSignAs);
2170 if (hdr->security & INLINE)
2171 fputc ('I', msg->fp);
2172 fputc ('\n', msg->fp);
2175 /* (postponment) if the mail is to be signed or encrypted, save this info */
2176 if (post && (hdr->security & APPLICATION_SMIME)) {
2177 fputs ("X-Mutt-SMIME: ", msg->fp);
2178 if (hdr->security & ENCRYPT) {
2179 fputc ('E', msg->fp);
2180 if (SmimeCryptAlg && *SmimeCryptAlg)
2181 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2183 if (hdr->security & SIGN) {
2184 fputc ('S', msg->fp);
2185 if (SmimeDefaultKey && *SmimeDefaultKey)
2186 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2188 if (hdr->security & INLINE)
2189 fputc ('I', msg->fp);
2190 fputc ('\n', msg->fp);
2193 /* (postponement) if the mail is to be sent through a mixmaster
2194 * chain, save that information
2196 if (post && hdr->chain && hdr->chain) {
2199 fputs ("X-Mutt-Mix:", msg->fp);
2200 for (p = hdr->chain; p; p = p->next)
2201 fprintf (msg->fp, " %s", (char *) p->data);
2203 fputc ('\n', msg->fp);
2207 char sasha[LONG_STRING];
2210 mutt_write_mime_body (hdr->content, tempfp);
2212 /* make sure the last line ends with a newline. Emacs doesn't ensure
2213 * this will happen, and it can cause problems parsing the mailbox
2216 fseeko (tempfp, -1, 2);
2217 if (fgetc (tempfp) != '\n') {
2218 fseeko (tempfp, 0, 2);
2219 fputc ('\n', tempfp);
2223 if (ferror (tempfp)) {
2226 mx_commit_message (msg, &f); /* XXX - really? */
2227 mx_close_message (&msg);
2228 mx_close_mailbox (&f, NULL);
2232 /* count the number of lines */
2234 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2236 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2237 fprintf (msg->fp, "Lines: %d\n\n", lines);
2239 /* copy the body and clean up */
2241 r = mutt_copy_stream (tempfp, msg->fp);
2242 if (m_fclose(&tempfp) != 0)
2244 /* if there was an error, leave the temp version */
2248 fputc ('\n', msg->fp); /* finish off the header */
2249 r = mutt_write_mime_body (hdr->content, msg->fp);
2252 if (mx_commit_message (msg, &f) != 0)
2254 mx_close_message (&msg);
2255 mx_close_mailbox (&f, NULL);
2258 set_noconv_flags (hdr->content, 0);