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.
12 #include <lib-lib/lib-lib.h>
15 #include <sys/utsname.h>
17 #include <lib-sys/exit.h>
18 #include <lib-sys/mutt_signal.h>
19 #include <lib-mime/mime.h>
20 #include <lib-ui/curses.h>
22 #include <lib-crypt/crypt.h>
28 #include "recvattach.h"
33 #include "mutt_idna.h"
36 # include "mutt_libesmtp.h"
37 #endif /* USE_LIBESMTP */
43 #ifdef HAVE_SYSEXITS_H
45 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
49 /* If you are debugging this file, comment out the following line. */
58 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
60 static void transform_to_7bit (BODY * a, FILE * fpin);
62 static void encode_quoted (fgetconv_t * fc, FILE * fout, int istext)
65 char line[77], savechar;
67 while ((c = fgetconv (fc)) != EOF) {
68 /* Wrap the line if needed. */
69 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
70 /* If the last character is "quoted", then be sure to move all three
71 * characters to the next line. Otherwise, just move the last
74 if (line[linelen - 3] == '=') {
75 line[linelen - 3] = 0;
80 line[1] = line[linelen - 2];
81 line[2] = line[linelen - 1];
85 savechar = line[linelen - 1];
86 line[linelen - 1] = '=';
95 /* Escape lines that begin with/only contain "the message separator". */
96 if (linelen == 4 && !m_strncmp("From", line, 4)) {
97 m_strcpy(line, sizeof(line), "=46rom");
100 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
101 m_strcpy(line, sizeof(line), "=66rom");
104 else if (linelen == 1 && line[0] == '.') {
105 m_strcpy(line, sizeof(line), "=2E");
110 if (c == '\n' && istext) {
111 /* Check to make sure there is no trailing space on this line. */
113 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
115 sprintf (line + linelen - 1, "=%2.2X",
116 (unsigned char) line[linelen - 1]);
120 savechar = line[linelen - 1];
122 line[linelen - 1] = '=';
125 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
135 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
136 /* Check to make sure there is enough room for the quoted character.
137 * If not, wrap to the next line.
140 line[linelen++] = '=';
146 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
150 /* Don't worry about wrapping the line here. That will happen during
151 * the next iteration when I'll also know what the next character is.
157 /* Take care of anything left in the buffer */
159 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
160 /* take care of trailing whitespace */
162 sprintf (line + linelen - 1, "=%2.2X",
163 (unsigned char) line[linelen - 1]);
165 savechar = line[linelen - 1];
166 line[linelen - 1] = '=';
170 sprintf (line, "=%2.2X", (unsigned char) savechar);
179 static char b64_buffer[3];
180 static short b64_num;
181 static short b64_linelen;
183 static void b64_flush (FILE * fout)
190 if (b64_linelen >= 72) {
195 for (i = b64_num; i < 3; i++)
196 b64_buffer[i] = '\0';
198 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
201 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
206 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
210 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
215 while (b64_linelen % 4) {
224 static void b64_putc (char c, FILE * fout)
229 b64_buffer[b64_num++] = c;
233 static void encode_base64 (fgetconv_t * fc, FILE * fout, int istext)
237 b64_num = b64_linelen = 0;
239 while ((ch = fgetconv (fc)) != EOF) {
240 if (istext && ch == '\n' && ch1 != '\r')
241 b64_putc ('\r', fout);
249 static void encode_8bit (fgetconv_t * fc, FILE * fout, int istext)
253 while ((ch = fgetconv (fc)) != EOF)
258 int mutt_write_mime_header (BODY * a, FILE * f)
267 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
272 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
274 for (p = a->parameter; p; p = p->next) {
283 tmp = m_strdup(p->value);
284 encode = rfc2231_encode_string (&tmp);
285 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
287 /* Dirty hack to make messages readable by Outlook Express
288 * for the Mac: force quotes around the boundary parameter
289 * even when they aren't needed.
292 if (!ascii_strcasecmp (p->attribute, "boundary")
293 && !strcmp (buffer, tmp))
294 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
298 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
300 if (len + tmplen + 2 > 76) {
309 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
317 fprintf (f, "Content-Description: %s\n", a->description);
319 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
322 if (!(fn = a->d_filename))
328 /* Strip off the leading path... */
329 if ((t = strrchr (fn, '/')))
336 encode = rfc2231_encode_string (&tmp);
337 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
339 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
345 if (a->encoding != ENC7BIT)
346 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
348 /* Do NOT add the terminator here!!! */
349 return (ferror (f) ? -1 : 0);
352 int mutt_write_mime_body (BODY * a, FILE * f)
355 char boundary[SHORT_STRING];
356 char send_charset[SHORT_STRING];
361 if (a->type == TYPEMULTIPART) {
362 /* First, find the boundary to use */
363 if (!(p = parameter_getval(a->parameter, "boundary"))) {
364 mutt_error _("No boundary parameter found! [report this error]");
368 m_strcpy(boundary, sizeof(boundary), p);
370 for (t = a->parts; t; t = t->next) {
371 fprintf (f, "\n--%s\n", boundary);
372 if (mutt_write_mime_header (t, f) == -1)
375 if (mutt_write_mime_body (t, f) == -1)
378 fprintf (f, "\n--%s--\n", boundary);
379 return (ferror (f) ? -1 : 0);
382 /* This is pretty gross, but it's the best solution for now... */
383 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
384 fputs ("Version: 1\n", f);
388 if ((fpin = fopen (a->filename, "r")) == NULL) {
389 mutt_error (_("%s no longer exists!"), a->filename);
393 if (a->type == TYPETEXT && (!a->noconv))
394 fc = fgetconv_open (fpin, a->file_charset,
395 mutt_get_body_charset (send_charset,
396 sizeof (send_charset), a), 0);
398 fc = fgetconv_open (fpin, 0, 0, 0);
400 #define write_as_text_part(a) (mutt_is_text_part(a) || mutt_is_application_pgp(a))
401 if (a->encoding == ENCQUOTEDPRINTABLE)
402 encode_quoted (fc, f, write_as_text_part (a));
403 else if (a->encoding == ENCBASE64)
404 encode_base64 (fc, f, write_as_text_part (a));
405 else if (a->type == TYPETEXT && (!a->noconv))
406 encode_8bit (fc, f, write_as_text_part (a));
408 mutt_copy_stream (fpin, f);
409 #undef write_as_text_part
411 fgetconv_close (&fc);
414 return (ferror (f) ? -1 : 0);
426 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
430 int whitespace = s->whitespace;
432 int linelen = s->linelen;
433 int was_cr = s->was_cr;
435 if (!d) { /* This signals EOF */
438 if (linelen > info->linemax)
439 info->linemax = linelen;
444 for (; dlen; d++, dlen--) {
457 if (linelen > info->linemax)
458 info->linemax = linelen;
473 if (linelen > info->linemax)
474 info->linemax = linelen;
479 else if (ch == '\r') {
487 else if (ch == '\t' || ch == '\f') {
491 else if (ch < 32 || ch == 127)
495 if ((ch == 'F') || (ch == 'f'))
505 if (linelen == 2 && ch != 'r')
507 else if (linelen == 3 && ch != 'o')
509 else if (linelen == 4) {
522 if (ch != ' ' && ch != '\t')
527 s->whitespace = whitespace;
529 s->linelen = linelen;
535 * Find the best charset conversion of the file from fromcode into one
536 * of the tocodes. If successful, set *tocode and CONTENT *info and
537 * return the number of characters converted inexactly. If no
538 * conversion was possible, return -1.
540 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
541 * which would otherwise prevent us from knowing the number of inexact
542 * conversions. Where the candidate target charset is UTF-8 we avoid
543 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
544 * fails with some libraries.
546 * We assume that the output from iconv is never more than 4 times as
547 * long as the input for any pair of charsets we might be interested
550 static ssize_t convert_file_to (FILE * file, const char *fromcode,
551 int ncodes, const char **tocodes,
552 int *tocode, CONTENT * info)
556 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
559 ssize_t ibl, obl, ubl, ubl1, n, ret;
562 CONTENT_STATE *states;
565 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
566 if (cd1 == MUTT_ICONV_ERROR)
569 cd = p_new(iconv_t, ncodes);
570 score = p_new(ssize_t, ncodes);
571 states = p_new(CONTENT_STATE, ncodes);
572 infos = p_new(CONTENT, ncodes);
574 for (i = 0; i < ncodes; i++)
575 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
576 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
578 /* Special case for conversion to UTF-8 */
579 cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
585 /* Try to fill input buffer */
586 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
589 /* Convert to UTF-8 */
591 ob = bufu, obl = sizeof (bufu);
592 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
593 assert (n == -1 || !n);
594 if (n == -1 && ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
595 assert (errno == EILSEQ ||
596 (errno == EINVAL && ib == bufi && ibl < ssizeof (bufi)));
602 /* Convert from UTF-8 */
603 for (i = 0; i < ncodes; i++)
604 if (cd[i] != MUTT_ICONV_ERROR && score[i] != -1) {
605 ub = bufu, ubl = ubl1;
606 ob = bufo, obl = sizeof (bufo);
607 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
613 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
616 else if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1)
617 /* Special case for conversion to UTF-8 */
618 update_content_info (&infos[i], &states[i], bufu, ubl1);
621 /* Save unused input */
622 memmove (bufi, ib, ibl);
623 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
630 /* Find best score */
632 for (i = 0; i < ncodes; i++) {
633 if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1) {
634 /* Special case for conversion to UTF-8 */
639 else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
641 else if (ret == -1 || score[i] < ret) {
649 memcpy (info, &infos[*tocode], sizeof (CONTENT));
650 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
654 for (i = 0; i < ncodes; i++)
655 if (cd[i] != MUTT_ICONV_ERROR)
667 #endif /* !HAVE_ICONV */
671 * Find the first of the fromcodes that gives a valid conversion and
672 * the best charset conversion of the file into one of the tocodes. If
673 * successful, set *fromcode and *tocode to dynamically allocated
674 * strings, set CONTENT *info, and return the number of characters
675 * converted inexactly. If no conversion was possible, return -1.
677 * Both fromcodes and tocodes may be colon-separated lists of charsets.
678 * However, if fromcode is zero then fromcodes is assumed to be the
679 * name of a single charset even if it contains a colon.
681 static ssize_t convert_file_from_to (FILE * file,
682 const char *fromcodes,
683 const char *tocodes, char **fromcode,
684 char **tocode, CONTENT * info)
692 /* Count the tocodes */
694 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
695 if ((c1 = strchr (c, ':')) == c)
701 tcode = p_new(char *, ncodes);
702 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
703 if ((c1 = strchr (c, ':')) == c)
705 tcode[i] = m_substrdup(c, c1);
710 /* Try each fromcode in turn */
711 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
712 if ((c1 = strchr (c, ':')) == c)
714 fcode = m_substrdup(c, c1);
716 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
728 /* There is only one fromcode */
729 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
738 for (i = 0; i < ncodes; i++)
747 * Analyze the contents of a file to determine which MIME encoding to use.
748 * Also set the body charset, sometimes, or not.
750 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
755 char *fromcode = NULL;
766 if (stat (fname, &sb) == -1) {
767 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
771 if (!S_ISREG (sb.st_mode)) {
772 mutt_error (_("%s isn't a regular file."), fname);
776 if ((fp = fopen (fname, "r")) == NULL) {
780 info = p_new(CONTENT, 1);
783 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
784 const char *chs = parameter_getval(b->parameter, "charset");
785 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
786 FileCharset : Charset) : Charset;
787 if (Charset && (chs || SendCharset) &&
788 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
789 &fromcode, &tocode, info) != -1) {
791 charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
792 parameter_setval(&b->parameter, "charset", chsbuf);
794 b->file_charset = fromcode;
802 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
803 update_content_info (info, &state, buffer, r);
804 update_content_info (info, &state, 0, 0);
808 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
809 parameter_setval(&b->parameter, "charset",
810 (!info->hibin ? "us-ascii"
811 : Charset && !charset_is_us_ascii(Charset) ? Charset : "unknown-8bit"));
816 /* Given a file with path ``s'', see if there is a registered MIME type.
817 * returns the major MIME type, and copies the subtype to ``d''. First look
818 * for ~/.mime.types, then look in a system mime.types if we can find one.
819 * The longest match is used so that we can match `ps.gz' when `gz' also
823 int mutt_lookup_mime_type (BODY * att, const char *path)
827 char buf[LONG_STRING];
828 char subtype[STRING], xtype[STRING];
830 int szf, sze, cur_sze;
838 szf = m_strlen(path);
840 for (count = 0; count < 4; count++) {
842 * can't use strtok() because we use it in an inner loop below, so use
843 * a switch statement here instead.
847 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
850 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
853 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
856 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
859 goto bye; /* shouldn't happen */
862 if ((f = fopen (buf, "r")) != NULL) {
863 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
864 /* weed out any comments */
865 if ((p = strchr (buf, '#')))
868 /* remove any leading space. */
869 ct = vskipspaces(buf);
871 /* position on the next field in this line */
872 if ((p = strpbrk (ct, " \t")) == NULL)
877 /* cycle through the file extensions */
878 while ((p = strtok (p, " \t\n"))) {
880 if ((sze > cur_sze) && (szf >= sze) &&
881 (m_strcasecmp(path + szf - sze, p) == 0
882 || ascii_strcasecmp (path + szf - sze, p) == 0)
883 && (szf == sze || path[szf - sze - 1] == '.'))
885 /* get the content-type */
887 if ((p = strchr (ct, '/')) == NULL) {
888 /* malformed line, just skip it. */
893 for (q = p; *q && !ISSPACE (*q); q++);
895 m_strncpy(subtype, sizeof(subtype), p, q - p);
897 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
898 m_strcpy(xtype, sizeof(xtype), ct);
911 if (type != TYPEOTHER || *xtype != '\0') {
913 m_strreplace(&att->subtype, subtype);
914 m_strreplace(&att->xtype, xtype);
920 void mutt_message_to_7bit (BODY * a, FILE * fp)
922 char temp[_POSIX_PATH_MAX];
928 if (!a->filename && fp)
930 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
931 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
936 if (stat (a->filename, &sb) == -1) {
937 mutt_perror ("stat");
940 a->length = sb.st_size;
944 if (!(fpout = safe_fopen (temp, "w+"))) {
945 mutt_perror ("fopen");
949 fseeko (fpin, a->offset, 0);
950 a->parts = mutt_parse_messageRFC822 (fpin, a);
952 transform_to_7bit (a->parts, fpin);
954 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
955 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
957 fputs ("MIME-Version: 1.0\n", fpout);
958 mutt_write_mime_header (a->parts, fpout);
960 mutt_write_mime_body (a->parts, fpout);
972 a->encoding = ENC7BIT;
973 a->d_filename = a->filename;
974 if (a->filename && a->unlink)
975 unlink (a->filename);
976 a->filename = m_strdup(temp);
978 if (stat (a->filename, &sb) == -1) {
979 mutt_perror ("stat");
982 a->length = sb.st_size;
983 body_list_wipe(&a->parts);
984 a->hdr->content = NULL;
987 static void transform_to_7bit (BODY * a, FILE * fpin)
989 char buff[_POSIX_PATH_MAX];
994 for (; a; a = a->next) {
995 if (a->type == TYPEMULTIPART) {
996 if (a->encoding != ENC7BIT)
997 a->encoding = ENC7BIT;
999 transform_to_7bit (a->parts, fpin);
1001 else if (mutt_is_message_type (a->type, a->subtype)) {
1002 mutt_message_to_7bit (a, fpin);
1006 a->force_charset = 1;
1009 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1010 mutt_perror ("fopen");
1014 mutt_decode_attachment (a, &s);
1016 a->d_filename = a->filename;
1017 a->filename = m_strdup(buff);
1019 if (stat (a->filename, &sb) == -1) {
1020 mutt_perror ("stat");
1023 a->length = sb.st_size;
1025 mutt_update_encoding (a);
1026 if (a->encoding == ENC8BIT)
1027 a->encoding = ENCQUOTEDPRINTABLE;
1028 else if (a->encoding == ENCBINARY)
1029 a->encoding = ENCBASE64;
1034 /* determine which Content-Transfer-Encoding to use */
1035 static void mutt_set_encoding (BODY * b, CONTENT * info)
1037 char send_charset[SHORT_STRING];
1039 if (b->type == TYPETEXT) {
1041 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1042 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1043 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1044 b->encoding = ENCQUOTEDPRINTABLE;
1045 else if (info->hibin)
1046 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1048 b->encoding = ENC7BIT;
1050 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1051 if (info->lobin || info->hibin) {
1052 if (option (OPTALLOW8BIT) && !info->lobin)
1053 b->encoding = ENC8BIT;
1055 mutt_message_to_7bit (b, NULL);
1058 b->encoding = ENC7BIT;
1060 else if (b->type == TYPEAPPLICATION
1061 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1062 b->encoding = ENC7BIT;
1065 /* Determine which encoding is smaller */
1066 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1067 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1068 b->encoding = ENCBASE64;
1070 b->encoding = ENCQUOTEDPRINTABLE;
1074 void mutt_stamp_attachment (BODY * a)
1076 a->stamp = time (NULL);
1079 /* Get a body's character set */
1081 char *mutt_get_body_charset(char *d, ssize_t dlen, BODY * b)
1085 if (b && b->type != TYPETEXT)
1088 p = b ? parameter_getval(b->parameter, "charset") : NULL;
1089 charset_canonicalize(d, dlen, p);
1094 /* Assumes called from send mode where BODY->filename points to actual file */
1095 void mutt_update_encoding (BODY * a)
1098 char chsbuff[STRING];
1100 /* override noconv when it's us-ascii */
1101 if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1104 if (!a->force_charset && !a->noconv)
1105 parameter_delval(&a->parameter, "charset");
1107 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1110 mutt_set_encoding (a, info);
1111 mutt_stamp_attachment (a);
1113 p_delete(&a->content);
1118 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1120 char buffer[LONG_STRING];
1123 int cmflags, chflags;
1124 int pgp = hdr->security;
1126 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1127 (hdr->security & ENCRYPT)) {
1128 if (!crypt_valid_passphrase (hdr->security))
1132 mutt_mktemp (buffer);
1133 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1137 body->type = TYPEMESSAGE;
1138 body->subtype = m_strdup("rfc822");
1139 body->filename = m_strdup(buffer);
1142 body->disposition = DISPINLINE;
1145 mutt_parse_mime_message (ctx, hdr);
1150 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1151 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1152 chflags |= CH_MIME | CH_TXTPLAIN;
1153 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1154 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1156 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1157 if (mutt_is_multipart_encrypted (hdr->content)) {
1158 chflags |= CH_MIME | CH_NONEWLINE;
1159 cmflags = M_CM_DECODE_PGP;
1162 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1163 chflags |= CH_MIME | CH_TXTPLAIN;
1164 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1167 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1168 chflags |= CH_MIME | CH_TXTPLAIN;
1169 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1170 pgp &= ~SMIMEENCRYPT;
1174 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1179 body->hdr = header_new();
1180 body->hdr->offset = 0;
1181 /* we don't need the user headers here */
1182 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1183 body->hdr->security = pgp;
1184 mutt_update_encoding (body);
1185 body->parts = body->hdr->content;
1192 BODY *mutt_make_file_attach (const char *path)
1198 att->filename = m_strdup(path);
1200 /* Attempt to determine the appropriate content-type based on the filename
1203 mutt_lookup_mime_type (att, path);
1205 if ((info = mutt_get_content_info (path, att)) == NULL) {
1206 body_list_wipe(&att);
1210 if (!att->subtype) {
1211 if (info->lobin == 0
1212 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1214 * Statistically speaking, there should be more than 10% "lobin"
1215 * chars if this is really a binary file...
1217 att->type = TYPETEXT;
1218 att->subtype = m_strdup("plain");
1220 att->type = TYPEAPPLICATION;
1221 att->subtype = m_strdup("octet-stream");
1225 mutt_update_encoding (att);
1229 static int get_toplevel_encoding (BODY * a)
1233 for (; a; a = a->next) {
1234 if (a->encoding == ENCBINARY)
1236 else if (a->encoding == ENC8BIT)
1243 BODY *mutt_make_multipart (BODY * b)
1248 new->type = TYPEMULTIPART;
1249 new->subtype = m_strdup("mixed");
1250 new->encoding = get_toplevel_encoding (b);
1251 parameter_set_boundary(&new->parameter);
1253 new->disposition = DISPINLINE;
1259 /* remove the multipart body if it exists */
1260 BODY *mutt_remove_multipart (BODY * b)
1273 char *mutt_make_date (char *s, ssize_t len)
1275 time_t t = time (NULL);
1276 struct tm *l = localtime (&t);
1277 time_t tz = mutt_local_tz (t);
1281 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1282 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1283 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1284 (int) tz / 60, (int) abs (tz) % 60);
1288 /* wrapper around mutt_write_address() so we can handle very large
1289 recipient lists without needing a huge temporary buffer in memory */
1290 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1294 char buf[LONG_STRING];
1302 rfc822_write_address (buf, sizeof (buf), adr, display);
1303 len = m_strlen(buf);
1304 if (count && linelen + len > 74) {
1306 linelen = len + 8; /* tab is usually about 8 spaces... */
1309 if (count && adr->mailbox) {
1317 if (!adr->group && adr->next && adr->next->mailbox) {
1327 /* arbitrary number of elements to grow the array by */
1332 /* need to write the list in reverse because they are stored in reverse order
1333 * when parsed to speed up threading
1335 void mutt_write_references (string_list_t * r, FILE * f)
1337 string_list_t **ref = NULL;
1338 int refcnt = 0, refmax = 0;
1340 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1341 if (refcnt == refmax)
1342 p_realloc(&ref, refmax += REF_INC);
1346 while (refcnt-- > 0) {
1348 fputs (ref[refcnt]->data, f);
1354 /* Note: all RFC2047 encoding should be done outside of this routine, except
1355 * for the "real name." This will allow this routine to be used more than
1356 * once, if necessary.
1358 * Likewise, all IDN processing should happen outside of this routine.
1360 * mode == 1 => "lite" mode (used for edit_hdrs)
1361 * mode == 0 => normal mode. write full header + MIME headers
1362 * mode == -1 => write just the envelope info (used for postponing messages)
1364 * privacy != 0 => will omit any headers which may identify the user.
1365 * Output generated is suitable for being sent through
1366 * anonymous remailer chains.
1370 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1371 int mode, int privacy)
1373 char buffer[LONG_STRING];
1375 string_list_t *tmp = env->userhdrs;
1376 int has_agent = 0; /* user defined user-agent header field exists */
1377 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1380 if (!option (OPTNEWSSEND))
1382 if (mode == 0 && !privacy)
1383 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1385 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1387 /* OPTUSEFROM is not consulted here so that we can still write a From:
1388 * field if the user sets it with the `my_hdr' command
1390 if (env->from && !privacy) {
1392 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1393 fprintf (fp, "From: %s\n", buffer);
1398 mutt_write_address_list (env->to, fp, 4, 0);
1402 if (!option (OPTNEWSSEND))
1404 if (EDIT_HEADER("To:"))
1405 fputs ("To:\n", fp);
1409 mutt_write_address_list (env->cc, fp, 4, 0);
1413 if (!option (OPTNEWSSEND))
1415 if (EDIT_HEADER("Cc:"))
1416 fputs ("Cc:\n", fp);
1419 if (mode != 0 || option (OPTWRITEBCC)) {
1420 fputs ("Bcc: ", fp);
1421 mutt_write_address_list (env->bcc, fp, 5, 0);
1426 if (!option (OPTNEWSSEND))
1428 if (EDIT_HEADER("Bcc:"))
1429 fputs ("Bcc:\n", fp);
1432 if (env->newsgroups)
1433 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1434 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1435 fputs ("Newsgroups:\n", fp);
1437 if (env->followup_to)
1438 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1439 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1440 fputs ("Followup-To:\n", fp);
1442 if (env->x_comment_to)
1443 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1444 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1445 EDIT_HEADER("X-Comment-To:"))
1446 fputs ("X-Comment-To:\n", fp);
1450 fprintf (fp, "Subject: %s\n", env->subject);
1451 else if (mode == 1 && EDIT_HEADER("Subject:"))
1452 fputs ("Subject:\n", fp);
1454 /* save message id if the user has set it */
1455 if (env->message_id && !privacy)
1456 fprintf (fp, "Message-ID: %s\n", env->message_id);
1458 if (env->reply_to) {
1459 fputs ("Reply-To: ", fp);
1460 mutt_write_address_list (env->reply_to, fp, 10, 0);
1462 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1463 fputs ("Reply-To:\n", fp);
1465 if (env->mail_followup_to)
1467 if (!option (OPTNEWSSEND))
1470 fputs ("Mail-Followup-To: ", fp);
1471 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1475 if (env->references) {
1476 fputs ("References:", fp);
1477 mutt_write_references (env->references, fp);
1481 /* Add the MIME headers */
1482 fputs ("MIME-Version: 1.0\n", fp);
1483 mutt_write_mime_header (attach, fp);
1486 if (env->in_reply_to) {
1487 fputs ("In-Reply-To:", fp);
1488 mutt_write_references (env->in_reply_to, fp);
1494 /* Add any user defined headers */
1495 for (; tmp; tmp = tmp->next) {
1496 if ((p = strchr (tmp->data, ':'))) {
1497 p = vskipspaces(p + 1);
1499 continue; /* don't emit empty fields. */
1501 /* check to see if the user has overridden the user-agent field */
1502 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1508 fputs (tmp->data, fp);
1513 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1516 if (OperatingSystem != NULL) {
1517 os = OperatingSystem;
1520 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1522 /* Add a vanity header */
1523 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1526 list_del (&hdrs, (list_del_t*)xmemfree);
1528 return (ferror (fp) == 0 ? 0 : -1);
1531 static void encode_headers (string_list_t * h)
1537 for (; h; h = h->next) {
1538 if (!(p = strchr (h->data, ':')))
1542 p = vskipspaces(p + 1);
1548 rfc2047_encode_string (&tmp);
1549 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1551 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1557 const char *mutt_fqdn (short may_hide_host)
1561 if (Fqdn && Fqdn[0] != '@') {
1564 if (may_hide_host && option (OPTHIDDENHOST)) {
1565 if ((p = strchr (Fqdn, '.')))
1568 /* sanity check: don't hide the host if
1569 * the fqdn is something like detebe.org.
1572 if (!p || !(q = strchr (p, '.')))
1580 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1581 static char mutt_normalized_char(char c)
1583 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1586 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1588 #define APPEND_FMT(fmt, arg) \
1590 int snlen = snprintf(buf, len, fmt, arg); \
1595 #define APPEND_BYTE(c) \
1609 static char MsgIdPfx = 'A';
1613 APPEND_BYTE(mutt_normalized_char(c));
1621 APPEND_FMT("%02d", tm->tm_mday);
1624 APPEND_FMT("%02d", tm->tm_hour);
1627 APPEND_FMT("%02d", tm->tm_mon + 1);
1630 APPEND_FMT("%02d", tm->tm_min);
1633 APPEND_FMT("%lo", (unsigned long)now);
1636 APPEND_FMT("%u", (unsigned int)getpid());
1639 APPEND_FMT("%c", MsgIdPfx);
1640 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1643 APPEND_FMT("%u", (unsigned int)rand());
1646 APPEND_FMT("%x", (unsigned int)rand());
1649 APPEND_FMT("%02d", tm->tm_sec);
1652 APPEND_FMT("%u", (unsigned int) now);
1655 APPEND_FMT("%x", (unsigned int) now);
1657 case 'Y': /* this will break in the year 10000 ;-) */
1658 APPEND_FMT("%04d", tm->tm_year + 1900);
1663 default: /* invalid formats are replaced by '.' */
1665 m_strncat(buf, len, ".", 1);
1675 static char *mutt_gen_msgid (void)
1677 char buf[SHORT_STRING];
1678 char localpart[SHORT_STRING];
1679 unsigned int localpart_length;
1682 if (!(fqdn = mutt_fqdn (0)))
1683 fqdn = NONULL (Hostname);
1685 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1687 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1689 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1690 return (m_strdup(buf));
1693 static RETSIGTYPE alarm_handler (int sig)
1698 /* invoke sendmail in a subshell
1699 path (in) path to program to execute
1700 args (in) arguments to pass to program
1701 msg (in) temp file containing message to send
1702 tempfile (out) if sendmail is put in the background, this points
1703 to the temporary file containing the stdout of the
1706 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1712 mutt_block_signals_system ();
1715 /* we also don't want to be stopped right now */
1716 sigaddset (&set, SIGTSTP);
1717 sigprocmask (SIG_BLOCK, &set, NULL);
1719 if (SendmailWait >= 0) {
1720 char tmp[_POSIX_PATH_MAX];
1723 *tempfile = m_strdup(tmp);
1726 if ((pid = fork ()) == 0) {
1727 struct sigaction act, oldalrm;
1729 /* save parent's ID before setsid() */
1732 /* we want the delivery to continue even after the main process dies,
1733 * so we put ourselves into another session right away
1737 /* next we close all open files */
1738 for (fd = 0; fd < getdtablesize(); fd++)
1741 /* now the second fork() */
1742 if ((pid = fork ()) == 0) {
1743 /* "msg" will be opened as stdin */
1744 if (open (msg, O_RDONLY, 0) < 0) {
1750 if (SendmailWait >= 0) {
1751 /* *tempfile will be opened as stdout */
1752 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1755 /* redirect stderr to *tempfile too */
1760 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1762 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1766 execv (path, (char**)args);
1769 else if (pid == -1) {
1775 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1776 * SendmailWait = 0: wait forever
1777 * SendmailWait < 0: don't wait
1779 if (SendmailWait > 0) {
1781 act.sa_handler = alarm_handler;
1783 /* need to make sure waitpid() is interrupted on SIGALRM */
1784 act.sa_flags = SA_INTERRUPT;
1788 sigemptyset (&act.sa_mask);
1789 sigaction (SIGALRM, &act, &oldalrm);
1790 alarm (SendmailWait);
1792 else if (SendmailWait < 0)
1793 _exit (0xff & EX_OK);
1795 if (waitpid (pid, &st, 0) > 0) {
1796 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1797 if (SendmailWait && st == (0xff & EX_OK)) {
1798 unlink (*tempfile); /* no longer needed */
1803 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1804 if (SendmailWait > 0) {
1810 /* reset alarm; not really needed, but... */
1812 sigaction (SIGALRM, &oldalrm, NULL);
1814 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1815 /* the parent is already dead */
1823 sigprocmask (SIG_UNBLOCK, &set, NULL);
1825 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1826 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1828 st = S_ERR; /* error */
1830 mutt_unblock_signals_system (1);
1835 static const char **
1836 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1838 for (; addr; addr = addr->next) {
1839 /* weed out group mailboxes, since those are for display only */
1840 if (addr->mailbox && !addr->group) {
1841 if (*argslen == *argsmax)
1842 p_realloc(&args, *argsmax += 5);
1843 args[(*argslen)++] = addr->mailbox;
1849 static const char **
1850 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1852 if (*argslen == *argsmax) {
1853 p_realloc(&args, *argsmax += 5);
1855 args[(*argslen)++] = s;
1859 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1860 address_t * to, address_t * cc, address_t * bcc, /* recips */
1861 const char *msg, /* file containing message */
1863 { /* message contains 8bit chars */
1864 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1865 const char **args = NULL;
1866 ssize_t argslen = 0, argsmax = 0;
1870 if (option (OPTNEWSSEND)) {
1871 char cmd[LONG_STRING];
1873 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1876 i = nntp_post (msg);
1885 s = m_strdup(Sendmail);
1889 while ((ps = strtok (ps, " "))) {
1890 if (argslen == argsmax)
1891 p_realloc(&args, argsmax += 5);
1894 args[argslen++] = ps;
1896 path = m_strdup(ps);
1897 ps = strrchr (ps, '/');
1902 args[argslen++] = ps;
1909 if (!option (OPTNEWSSEND)) {
1911 if (eightbit && option (OPTUSE8BITMIME))
1912 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1914 if (option (OPTENVFROM)) {
1915 address_t *f = NULL;
1918 else if (from && !from->next)
1921 args = add_option (args, &argslen, &argsmax, "-f");
1922 args = add_args (args, &argslen, &argsmax, f);
1926 args = add_option (args, &argslen, &argsmax, "-N");
1927 args = add_option (args, &argslen, &argsmax, DsnNotify);
1930 args = add_option (args, &argslen, &argsmax, "-R");
1931 args = add_option (args, &argslen, &argsmax, DsnReturn);
1933 args = add_option (args, &argslen, &argsmax, "--");
1934 args = add_args (args, &argslen, &argsmax, to);
1935 args = add_args (args, &argslen, &argsmax, cc);
1936 args = add_args (args, &argslen, &argsmax, bcc);
1941 if (argslen == argsmax)
1942 p_realloc(&args, ++argsmax);
1944 args[argslen++] = NULL;
1946 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
1948 mutt_error (_("Error sending message, child exited %d (%s)."), i,
1953 if (stat (childout, &st) == 0 && st.st_size > 0)
1954 mutt_do_pager (_("Output of the delivery process"), childout, 0,
1962 p_delete(&childout);
1967 if (i == (EX_OK & 0xff))
1969 else if (i == S_BKG)
1976 int mutt_invoke_mta (address_t * from, /* the sender */
1977 address_t * to, address_t * cc, address_t * bcc, /* recips */
1978 const char *msg, /* file containing message */
1980 { /* message contains 8bit chars */
1983 if (!option (OPTNEWSSEND))
1986 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
1989 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
1992 /* For postponing (!final) do the necessary encodings only */
1993 void mutt_prepare_envelope (ENVELOPE * env, int final)
1995 char buffer[LONG_STRING];
1998 if (env->bcc && !(env->to || env->cc)) {
1999 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2000 * recipients if there is no To: or Cc: field, so attempt to suppress
2001 * it by using an empty To: field.
2003 env->to = address_new ();
2005 env->to->next = address_new ();
2008 rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
2011 env->to->mailbox = m_strdup(buffer);
2014 mutt_set_followup_to (env);
2016 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2017 env->message_id = mutt_gen_msgid ();
2020 /* Take care of 8-bit => 7-bit conversion. */
2021 rfc2047_encode_adrlist (env->to, "To");
2022 rfc2047_encode_adrlist (env->cc, "Cc");
2023 rfc2047_encode_adrlist (env->bcc, "Bcc");
2024 rfc2047_encode_adrlist (env->from, "From");
2025 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2026 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2030 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2033 rfc2047_encode_string (&env->subject);
2035 encode_headers (env->userhdrs);
2038 void mutt_unprepare_envelope (ENVELOPE * env)
2040 string_list_t *item;
2042 for (item = env->userhdrs; item; item = item->next)
2043 rfc2047_decode (&item->data);
2045 address_list_wipe(&env->mail_followup_to);
2047 /* back conversions */
2048 rfc2047_decode_adrlist (env->to);
2049 rfc2047_decode_adrlist (env->cc);
2050 rfc2047_decode_adrlist (env->bcc);
2051 rfc2047_decode_adrlist (env->from);
2052 rfc2047_decode_adrlist (env->reply_to);
2053 rfc2047_decode (&env->subject);
2056 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2057 const char *resent_from, address_t * env_from)
2061 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2062 MESSAGE *msg = NULL;
2065 /* Try to bounce each message out, aborting if we get any failures. */
2066 for (i = 0; i < Context->msgcount; i++)
2067 if (Context->hdrs[i]->tagged)
2069 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2074 /* If we failed to open a message, return with error */
2075 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2081 mutt_mktemp (tempfile);
2082 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2083 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2085 if (!option (OPTBOUNCEDELIVERED))
2086 ch_flags |= CH_WEED_DELIVERED;
2088 fseeko (fp, h->offset, 0);
2089 fprintf (f, "Resent-From: %s", resent_from);
2090 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2091 if (MsgIdFormat && *MsgIdFormat)
2092 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2093 fputs ("Resent-To: ", f);
2094 mutt_write_address_list (to, f, 11, 0);
2095 mutt_copy_header (fp, h, f, ch_flags, NULL);
2097 mutt_copy_bytes (fp, f, h->content->length);
2100 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2101 h->content->encoding == ENC8BIT);
2105 mx_close_message (&msg);
2110 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2113 const char *fqdn = mutt_fqdn (1);
2114 char resent_from[STRING];
2118 resent_from[0] = '\0';
2119 from = mutt_default_from ();
2122 rfc822_qualify (from, fqdn);
2124 rfc2047_encode_adrlist (from, "Resent-From");
2125 if (mutt_addrlist_to_idna (from, &err)) {
2126 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2129 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2132 unset_option (OPTNEWSSEND);
2135 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2137 address_list_wipe(&from);
2142 static void set_noconv_flags (BODY * b, short flag)
2144 for (; b; b = b->next) {
2145 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2146 set_noconv_flags (b->parts, flag);
2147 else if (b->type == TYPETEXT && b->noconv) {
2148 parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2153 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2154 int post, char *fcc)
2158 char tempfile[_POSIX_PATH_MAX];
2159 FILE *tempfp = NULL;
2163 set_noconv_flags (hdr->content, 1);
2165 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2169 /* We need to add a Content-Length field to avoid problems where a line in
2170 * the message body begins with "From "
2172 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2173 mutt_mktemp (tempfile);
2174 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2175 mutt_perror (tempfile);
2176 mx_close_mailbox (&f, NULL);
2181 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2182 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2183 mx_close_mailbox (&f, NULL);
2187 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2188 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2190 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2193 /* (postponment) if this was a reply of some sort, <msgid> contians the
2194 * Message-ID: of message replied to. Save it using a special X-Mutt-
2195 * header so it can be picked up if the message is recalled at a later
2196 * point in time. This will allow the message to be marked as replied if
2197 * the same mailbox is still open.
2200 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2202 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2203 * it can be picked up when the message is recalled
2206 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2207 fprintf (msg->fp, "Status: RO\n");
2211 /* (postponment) if the mail is to be signed or encrypted, save this info */
2212 if (post && (hdr->security & APPLICATION_PGP)) {
2213 fputs ("X-Mutt-PGP: ", msg->fp);
2214 if (hdr->security & ENCRYPT)
2215 fputc ('E', msg->fp);
2216 if (hdr->security & SIGN) {
2217 fputc ('S', msg->fp);
2218 if (PgpSignAs && *PgpSignAs)
2219 fprintf (msg->fp, "<%s>", PgpSignAs);
2221 if (hdr->security & INLINE)
2222 fputc ('I', msg->fp);
2223 fputc ('\n', msg->fp);
2226 /* (postponment) if the mail is to be signed or encrypted, save this info */
2227 if (post && (hdr->security & APPLICATION_SMIME)) {
2228 fputs ("X-Mutt-SMIME: ", msg->fp);
2229 if (hdr->security & ENCRYPT) {
2230 fputc ('E', msg->fp);
2231 if (SmimeCryptAlg && *SmimeCryptAlg)
2232 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2234 if (hdr->security & SIGN) {
2235 fputc ('S', msg->fp);
2236 if (SmimeDefaultKey && *SmimeDefaultKey)
2237 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2239 if (hdr->security & INLINE)
2240 fputc ('I', msg->fp);
2241 fputc ('\n', msg->fp);
2245 /* (postponement) if the mail is to be sent through a mixmaster
2246 * chain, save that information
2249 if (post && hdr->chain && hdr->chain) {
2252 fputs ("X-Mutt-Mix:", msg->fp);
2253 for (p = hdr->chain; p; p = p->next)
2254 fprintf (msg->fp, " %s", (char *) p->data);
2256 fputc ('\n', msg->fp);
2261 char sasha[LONG_STRING];
2264 mutt_write_mime_body (hdr->content, tempfp);
2266 /* make sure the last line ends with a newline. Emacs doesn't ensure
2267 * this will happen, and it can cause problems parsing the mailbox
2270 fseeko (tempfp, -1, 2);
2271 if (fgetc (tempfp) != '\n') {
2272 fseeko (tempfp, 0, 2);
2273 fputc ('\n', tempfp);
2277 if (ferror (tempfp)) {
2280 mx_commit_message (msg, &f); /* XXX - really? */
2281 mx_close_message (&msg);
2282 mx_close_mailbox (&f, NULL);
2286 /* count the number of lines */
2288 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2290 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2291 fprintf (msg->fp, "Lines: %d\n\n", lines);
2293 /* copy the body and clean up */
2295 r = mutt_copy_stream (tempfp, msg->fp);
2296 if (fclose (tempfp) != 0)
2298 /* if there was an error, leave the temp version */
2303 fputc ('\n', msg->fp); /* finish off the header */
2304 r = mutt_write_mime_body (hdr->content, msg->fp);
2307 if (mx_commit_message (msg, &f) != 0)
2309 mx_close_message (&msg);
2310 mx_close_mailbox (&f, NULL);
2313 set_noconv_flags (hdr->content, 0);