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>
24 #include "recvattach.h"
28 #include "mutt_idna.h"
29 #include "send_smtp.h"
32 #include <nntp/nntp.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);
1420 if (env->x_comment_to)
1421 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1422 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1423 edit_header(mode, "X-Comment-To:"))
1424 fputs ("X-Comment-To:\n", fp);
1428 fprintf (fp, "Subject: %s\n", env->subject);
1429 else if (mode == 1 && edit_header(mode, "Subject:"))
1430 fputs ("Subject:\n", fp);
1432 /* save message id if the user has set it */
1433 if (env->message_id && !privacy)
1434 fprintf (fp, "Message-ID: %s\n", env->message_id);
1436 if (env->reply_to) {
1437 fputs ("Reply-To: ", fp);
1438 mutt_write_address_list (env->reply_to, fp, 10, 0);
1440 else if (mode > 0 && edit_header(mode, "Reply-To:"))
1441 fputs ("Reply-To:\n", fp);
1443 if (env->mail_followup_to)
1445 if (!option (OPTNEWSSEND))
1448 fputs ("Mail-Followup-To: ", fp);
1449 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1453 if (env->references) {
1454 fputs ("References:", fp);
1455 mutt_write_references (env->references, fp);
1459 /* Add the MIME headers */
1460 fputs ("MIME-Version: 1.0\n", fp);
1461 mutt_write_mime_header (attach, fp);
1464 if (env->in_reply_to) {
1465 fputs ("In-Reply-To:", fp);
1466 mutt_write_references (env->in_reply_to, fp);
1470 /* Add any user defined headers */
1471 for (; tmp; tmp = tmp->next) {
1472 if ((p = strchr (tmp->data, ':'))) {
1473 p = vskipspaces(p + 1);
1475 continue; /* don't emit empty fields. */
1477 /* check to see if the user has overridden the user-agent field */
1478 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1484 fputs (tmp->data, fp);
1489 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1490 if (mod_core.operating_system) {
1491 fprintf(fp, "User-Agent: %s (%s)\n", mutt_make_version(),
1492 mod_core.operating_system);
1494 fprintf(fp, "User-Agent: %s\n", mutt_make_version());
1498 return (ferror (fp) == 0 ? 0 : -1);
1501 static void encode_headers (string_list_t * h)
1507 for (; h; h = h->next) {
1508 if (!(p = strchr (h->data, ':')))
1512 p = vskipspaces(p + 1);
1518 rfc2047_encode_string (&tmp);
1519 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1521 sprintf (h->data + i, ": %s", NONULL (tmp));
1527 const char *mutt_fqdn(short may_hide_host)
1531 if (mod_core.hostname && mod_core.hostname[0] != '@') {
1532 p = mod_core.hostname;
1534 if (may_hide_host && option (OPTHIDDENHOST)) {
1535 if ((p = strchr(mod_core.hostname, '.')))
1538 /* sanity check: don't hide the host if
1539 the fqdn is something like detebe.org. */
1541 if (!p || !(q = strchr(p, '.')))
1542 p = mod_core.hostname;
1549 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1551 #define APPEND_FMT(fmt, arg) \
1553 int snlen = snprintf(buf, len, fmt, arg); \
1558 #define APPEND_BYTE(c) \
1572 static char MsgIdPfx = 'A';
1576 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1577 APPEND_BYTE((isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.');
1585 APPEND_FMT("%02d", tm->tm_mday);
1588 APPEND_FMT("%02d", tm->tm_hour);
1591 APPEND_FMT("%02d", tm->tm_mon + 1);
1594 APPEND_FMT("%02d", tm->tm_min);
1597 APPEND_FMT("%lo", (unsigned long)now);
1600 APPEND_FMT("%u", (unsigned int)getpid());
1603 APPEND_FMT("%c", MsgIdPfx);
1604 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1607 APPEND_FMT("%u", (unsigned int)rand());
1610 APPEND_FMT("%x", (unsigned int)rand());
1613 APPEND_FMT("%02d", tm->tm_sec);
1616 APPEND_FMT("%u", (unsigned int) now);
1619 APPEND_FMT("%x", (unsigned int) now);
1621 case 'Y': /* this will break in the year 10000 ;-) */
1622 APPEND_FMT("%04d", tm->tm_year + 1900);
1627 default: /* invalid formats are replaced by '.' */
1638 static char *mutt_gen_msgid (void)
1641 char localpart[STRING];
1644 if (!(fqdn = mutt_fqdn(0)))
1645 fqdn = NONULL(mod_core.shorthost);
1647 mutt_gen_localpart(localpart, sizeof(localpart), MsgIdFormat);
1648 snprintf(buf, sizeof(buf), "<%s@%s>", localpart, fqdn);
1649 return m_strdup(buf);
1652 static void alarm_handler (int sig __attribute__ ((unused)))
1657 /* invoke sendmail in a subshell
1658 path (in) path to program to execute
1659 args (in) arguments to pass to program
1660 msg (in) temp file containing message to send
1661 tempfile (out) if sendmail is put in the background, this points
1662 to the temporary file containing the stdout of the
1665 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1671 mutt_block_signals_system ();
1674 /* we also don't want to be stopped right now */
1675 sigaddset (&set, SIGTSTP);
1676 sigprocmask (SIG_BLOCK, &set, NULL);
1678 if (MTransport.sendmail_wait >= 0) {
1679 char tmp[_POSIX_PATH_MAX];
1682 *tempfile = m_strdup(tmp);
1685 if ((pid = fork ()) == 0) {
1686 struct sigaction act, oldalrm;
1688 /* save parent's ID before setsid() */
1691 /* we want the delivery to continue even after the main process dies,
1692 * so we put ourselves into another session right away
1696 /* next we close all open files */
1697 for (fd = 0; fd < getdtablesize(); fd++)
1700 /* now the second fork() */
1701 if ((pid = fork ()) == 0) {
1702 /* "msg" will be opened as stdin */
1703 if (open (msg, O_RDONLY, 0) < 0) {
1709 if (MTransport.sendmail_wait >= 0) {
1710 /* *tempfile will be opened as stdout */
1711 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1714 /* redirect stderr to *tempfile too */
1718 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1720 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1724 execv (path, (char**)args);
1727 else if (pid == -1) {
1733 /* sendmail_wait > 0: interrupt waitpid() after sendmail_wait seconds
1734 * sendmail_wait = 0: wait forever
1735 * sendmail_wait < 0: don't wait
1737 if (MTransport.sendmail_wait > 0) {
1739 act.sa_handler = alarm_handler;
1741 /* need to make sure waitpid() is interrupted on SIGALRM */
1742 act.sa_flags = SA_INTERRUPT;
1746 sigemptyset (&act.sa_mask);
1747 sigaction (SIGALRM, &act, &oldalrm);
1748 alarm (MTransport.sendmail_wait);
1750 else if (MTransport.sendmail_wait < 0)
1751 _exit (0xff & EX_OK);
1753 if (waitpid (pid, &st, 0) > 0) {
1754 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1755 if (MTransport.sendmail_wait && st == (0xff & EX_OK)) {
1756 unlink (*tempfile); /* no longer needed */
1760 st = (MTransport.sendmail_wait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1761 if (MTransport.sendmail_wait > 0) {
1767 /* reset alarm; not really needed, but... */
1769 sigaction (SIGALRM, &oldalrm, NULL);
1771 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1772 /* the parent is already dead */
1780 sigprocmask (SIG_UNBLOCK, &set, NULL);
1782 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1783 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1785 st = S_ERR; /* error */
1787 mutt_unblock_signals_system (1);
1792 static const char **
1793 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1795 for (; addr; addr = addr->next) {
1796 /* weed out group mailboxes, since those are for display only */
1797 if (addr->mailbox && !addr->group) {
1798 if (*argslen == *argsmax)
1799 p_realloc(&args, *argsmax += 5);
1800 args[(*argslen)++] = addr->mailbox;
1806 static const char **
1807 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1809 if (*argslen == *argsmax) {
1810 p_realloc(&args, *argsmax += 5);
1812 args[(*argslen)++] = s;
1816 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1817 address_t * to, address_t * cc, address_t * bcc, /* recips */
1818 const char *msg, /* file containing message */
1820 { /* message contains 8bit chars */
1821 char cmd[LONG_STRING];
1822 char *ps = NULL, *path = NULL, *childout = NULL;
1823 const char **args = NULL;
1824 ssize_t argslen = 0, argsmax = 0;
1828 if (option (OPTNEWSSEND)) {
1829 m_strformat(cmd, sizeof(cmd), 0, Inews, nntp_format_str, 0, 0);
1830 if (m_strisempty(cmd)) {
1831 i = nntp_post (msg);
1838 m_strcpy(cmd, sizeof(cmd), MTransport.sendmail);
1843 while ((ps = strtok(ps, " "))) {
1844 if (argslen == argsmax)
1845 p_realloc(&args, argsmax += 5);
1848 args[argslen++] = ps;
1850 path = m_strdup(ps);
1851 ps = strrchr (ps, '/');
1856 args[argslen++] = ps;
1863 if (!option (OPTNEWSSEND)) {
1865 if (eightbit && MTransport.use_8bitmime)
1866 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1868 if (MTransport.use_envelope_from) {
1869 address_t *f = MTransport.envelope_from_address;
1870 if (!f && from && !from->next)
1873 args = add_option (args, &argslen, &argsmax, "-f");
1874 args = add_args (args, &argslen, &argsmax, f);
1877 if (MTransport.dsn_notify) {
1878 args = add_option (args, &argslen, &argsmax, "-N");
1879 args = add_option (args, &argslen, &argsmax, MTransport.dsn_notify);
1881 if (MTransport.dsn_return) {
1882 args = add_option (args, &argslen, &argsmax, "-R");
1883 args = add_option (args, &argslen, &argsmax, MTransport.dsn_return);
1885 args = add_option (args, &argslen, &argsmax, "--");
1886 args = add_args (args, &argslen, &argsmax, to);
1887 args = add_args (args, &argslen, &argsmax, cc);
1888 args = add_args (args, &argslen, &argsmax, bcc);
1893 if (argslen >= argsmax)
1894 p_realloc(&args, ++argsmax);
1896 args[argslen++] = NULL;
1898 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
1900 mutt_error (_("Error sending message, child exited %d (%s)."), i,
1905 if (!stat(childout, &st) && st.st_size > 0)
1906 mutt_pager(_("Output of the delivery process"), childout, 0, NULL);
1913 p_delete(&childout);
1917 if (i == (EX_OK & 0xff))
1919 else if (i == S_BKG)
1926 int mutt_invoke_mta (address_t * from, /* the sender */
1927 address_t * to, address_t * cc, address_t * bcc, /* recips */
1928 const char *msg, /* file containing message */
1930 { /* message contains 8bit chars */
1933 if (!option (OPTNEWSSEND))
1936 return send_smtp_invoke (from, to, cc, bcc, msg, eightbit);
1939 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
1942 /* For postponing (!final) do the necessary encodings only */
1943 void mutt_prepare_envelope (ENVELOPE * env, int final)
1946 if (env->bcc && !(env->to || env->cc)) {
1947 /* some MTA's will put an Apparently-To: header field showing the Bcc:
1948 * recipients if there is no To: or Cc: field, so attempt to suppress
1949 * it by using an empty To: field.
1951 env->to = address_new();
1953 env->to->next = address_new();
1954 env->to->mailbox = m_strdup("undisclosed-recipients");
1957 mutt_set_followup_to(env);
1959 if (!env->message_id && !m_strisempty(MsgIdFormat))
1960 env->message_id = mutt_gen_msgid();
1963 /* Take care of 8-bit => 7-bit conversion. */
1964 rfc2047_encode_adrlist(env->to, "To");
1965 rfc2047_encode_adrlist(env->cc, "Cc");
1966 rfc2047_encode_adrlist(env->bcc, "Bcc");
1967 rfc2047_encode_adrlist(env->from, "From");
1968 rfc2047_encode_adrlist(env->mail_followup_to, "Mail-Followup-To");
1969 rfc2047_encode_adrlist(env->reply_to, "Reply-To");
1972 rfc2047_encode_string (&env->subject);
1973 encode_headers (env->userhdrs);
1976 void mutt_unprepare_envelope (ENVELOPE * env)
1978 string_list_t *item;
1980 for (item = env->userhdrs; item; item = item->next)
1981 rfc2047_decode(&item->data);
1983 address_list_wipe(&env->mail_followup_to);
1985 /* back conversions */
1986 rfc2047_decode_adrlist(env->to);
1987 rfc2047_decode_adrlist(env->cc);
1988 rfc2047_decode_adrlist(env->bcc);
1989 rfc2047_decode_adrlist(env->from);
1990 rfc2047_decode_adrlist(env->reply_to);
1991 rfc2047_decode(&env->subject);
1994 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
1995 const char *resent_from, address_t * env_from)
1999 char date[STRING], tempfile[_POSIX_PATH_MAX];
2000 MESSAGE *msg = NULL;
2003 /* Try to bounce each message out, aborting if we get any failures. */
2004 for (i = 0; i < Context->msgcount; i++)
2005 if (Context->hdrs[i]->tagged)
2007 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2012 /* If we failed to open a message, return with error */
2013 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2019 f = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
2021 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2023 if (!option (OPTBOUNCEDELIVERED))
2024 ch_flags |= CH_WEED_DELIVERED;
2026 fseeko (fp, h->offset, 0);
2027 fprintf (f, "Resent-From: %s", resent_from);
2028 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2029 if (!m_strisempty(MsgIdFormat))
2030 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid());
2031 fputs ("Resent-To: ", f);
2032 mutt_write_address_list (to, f, 11, 0);
2033 mutt_copy_header (fp, h, f, ch_flags, NULL);
2035 mutt_copy_bytes (fp, f, h->content->length);
2038 ret = mutt_invoke_mta(env_from, to, NULL, NULL, tempfile,
2039 h->content->encoding == ENC8BIT);
2043 mx_close_message (&msg);
2048 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2051 char resent_from[STRING];
2055 resent_from[0] = '\0';
2056 from = mutt_default_from ();
2058 rfc822_qualify(from, mutt_fqdn(1));
2060 rfc2047_encode_adrlist(from, "Resent-From");
2061 if (mutt_addrlist_to_idna (from, &err)) {
2062 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2065 rfc822_addrcat(resent_from, sizeof(resent_from), from, 0);
2068 unset_option (OPTNEWSSEND);
2071 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2073 address_list_wipe(&from);
2078 static void set_noconv_flags (BODY * b, short flag)
2080 for (; b; b = b->next) {
2081 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2082 set_noconv_flags (b->parts, flag);
2083 else if (b->type == TYPETEXT && b->noconv) {
2084 parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2089 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2090 int post, char *fcc)
2094 char tempfile[_POSIX_PATH_MAX];
2095 FILE *tempfp = NULL;
2099 set_noconv_flags (hdr->content, 1);
2101 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2105 /* We need to add a Content-Length field to avoid problems where a line in
2106 * the message body begins with "From "
2108 if (f.magic == M_MBOX) {
2109 tempfp = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
2111 mutt_error(_("Could not create temporary file"));
2112 mx_close_mailbox (&f, NULL);
2117 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2118 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2119 mx_close_mailbox (&f, NULL);
2123 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2124 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2126 mutt_write_rfc822_header(msg->fp, hdr->env, hdr->content, -post, 0);
2128 /* (postponment) if this was a reply of some sort, <msgid> contians the
2129 * Message-ID: of message replied to. Save it using a special X-Mutt-
2130 * header so it can be picked up if the message is recalled at a later
2131 * point in time. This will allow the message to be marked as replied if
2132 * the same mailbox is still open.
2135 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2137 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2138 * it can be picked up when the message is recalled
2141 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2142 fprintf (msg->fp, "Status: RO\n");
2144 /* (postponment) if the mail is to be signed or encrypted, save this info */
2145 if (post && (hdr->security & APPLICATION_PGP)) {
2146 fputs ("X-Mutt-PGP: ", msg->fp);
2147 if (hdr->security & ENCRYPT)
2148 fputc ('E', msg->fp);
2149 if (hdr->security & SIGN) {
2150 fputc ('S', msg->fp);
2151 if (PgpSignAs && *PgpSignAs)
2152 fprintf (msg->fp, "<%s>", PgpSignAs);
2154 if (hdr->security & INLINE)
2155 fputc ('I', msg->fp);
2156 fputc ('\n', msg->fp);
2159 /* (postponment) if the mail is to be signed or encrypted, save this info */
2160 if (post && (hdr->security & APPLICATION_SMIME)) {
2161 fputs ("X-Mutt-SMIME: ", msg->fp);
2162 if (hdr->security & ENCRYPT) {
2163 fputc ('E', msg->fp);
2164 if (SmimeCryptAlg && *SmimeCryptAlg)
2165 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2167 if (hdr->security & SIGN) {
2168 fputc ('S', msg->fp);
2169 if (SmimeDefaultKey && *SmimeDefaultKey)
2170 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2172 if (hdr->security & INLINE)
2173 fputc ('I', msg->fp);
2174 fputc ('\n', msg->fp);
2177 /* (postponement) if the mail is to be sent through a mixmaster
2178 * chain, save that information
2180 if (post && hdr->chain && hdr->chain) {
2183 fputs ("X-Mutt-Mix:", msg->fp);
2184 for (p = hdr->chain; p; p = p->next)
2185 fprintf (msg->fp, " %s", (char *) p->data);
2187 fputc ('\n', msg->fp);
2191 char sasha[LONG_STRING];
2194 mutt_write_mime_body (hdr->content, tempfp);
2196 /* make sure the last line ends with a newline. Emacs doesn't ensure
2197 * this will happen, and it can cause problems parsing the mailbox
2200 fseeko (tempfp, -1, 2);
2201 if (fgetc (tempfp) != '\n') {
2202 fseeko (tempfp, 0, 2);
2203 fputc ('\n', tempfp);
2207 if (ferror (tempfp)) {
2210 mx_commit_message (msg, &f); /* XXX - really? */
2211 mx_close_message (&msg);
2212 mx_close_mailbox (&f, NULL);
2216 /* count the number of lines */
2218 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2220 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2221 fprintf (msg->fp, "Lines: %d\n\n", lines);
2223 /* copy the body and clean up */
2225 r = mutt_copy_stream (tempfp, msg->fp);
2226 if (m_fclose(&tempfp) != 0)
2228 /* if there was an error, leave the temp version */
2232 fputc ('\n', msg->fp); /* finish off the header */
2233 r = mutt_write_mime_body (hdr->content, msg->fp);
2236 if (mx_commit_message (msg, &f) != 0)
2238 mx_close_message (&msg);
2239 mx_close_mailbox (&f, NULL);
2242 set_noconv_flags (hdr->content, 0);