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.
16 #include <lib-lib/mem.h>
17 #include <lib-lib/ascii.h>
18 #include <lib-lib/str.h>
19 #include <lib-lib/macros.h>
20 #include <lib-lib/file.h>
22 #include <lib-mime/mime.h>
26 #include "recvattach.h"
27 #include "mutt_curses.h"
33 #include "mutt_crypt.h"
34 #include "mutt_idna.h"
36 #include "lib/debug.h"
47 #include <sys/utsname.h>
50 # include "mutt_libesmtp.h"
51 #endif /* USE_LIBESMTP */
57 #ifdef HAVE_SYSEXITS_H
59 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
63 /* If you are debugging this file, comment out the following line. */
72 extern char RFC822Specials[];
74 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
76 static char MsgIdPfx = 'A';
78 static void transform_to_7bit (BODY * a, FILE * fpin);
80 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
83 char line[77], savechar;
85 while ((c = fgetconv (fc)) != EOF) {
86 /* Wrap the line if needed. */
87 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
88 /* If the last character is "quoted", then be sure to move all three
89 * characters to the next line. Otherwise, just move the last
92 if (line[linelen - 3] == '=') {
93 line[linelen - 3] = 0;
98 line[1] = line[linelen - 2];
99 line[2] = line[linelen - 1];
103 savechar = line[linelen - 1];
104 line[linelen - 1] = '=';
113 /* Escape lines that begin with/only contain "the message separator". */
114 if (linelen == 4 && !m_strncmp("From", line, 4)) {
115 m_strcpy(line, sizeof(line), "=46rom");
118 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
119 m_strcpy(line, sizeof(line), "=66rom");
122 else if (linelen == 1 && line[0] == '.') {
123 m_strcpy(line, sizeof(line), "=2E");
128 if (c == '\n' && istext) {
129 /* Check to make sure there is no trailing space on this line. */
131 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
133 sprintf (line + linelen - 1, "=%2.2X",
134 (unsigned char) line[linelen - 1]);
138 int savechar = line[linelen - 1];
140 line[linelen - 1] = '=';
143 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
153 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
154 /* Check to make sure there is enough room for the quoted character.
155 * If not, wrap to the next line.
158 line[linelen++] = '=';
164 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
168 /* Don't worry about wrapping the line here. That will happen during
169 * the next iteration when I'll also know what the next character is.
175 /* Take care of anything left in the buffer */
177 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
178 /* take care of trailing whitespace */
180 sprintf (line + linelen - 1, "=%2.2X",
181 (unsigned char) line[linelen - 1]);
183 savechar = line[linelen - 1];
184 line[linelen - 1] = '=';
188 sprintf (line, "=%2.2X", (unsigned char) savechar);
197 static char b64_buffer[3];
198 static short b64_num;
199 static short b64_linelen;
201 static void b64_flush (FILE * fout)
208 if (b64_linelen >= 72) {
213 for (i = b64_num; i < 3; i++)
214 b64_buffer[i] = '\0';
216 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
219 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
224 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
228 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
233 while (b64_linelen % 4) {
242 static void b64_putc (char c, FILE * fout)
247 b64_buffer[b64_num++] = c;
251 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
255 b64_num = b64_linelen = 0;
257 while ((ch = fgetconv (fc)) != EOF) {
258 if (istext && ch == '\n' && ch1 != '\r')
259 b64_putc ('\r', fout);
267 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
271 while ((ch = fgetconv (fc)) != EOF)
276 int mutt_write_mime_header (BODY * a, FILE * f)
286 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
289 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
291 for (p = a->parameter; p; p = p->next) {
300 tmp = m_strdup(p->value);
301 encode = rfc2231_encode_string (&tmp);
302 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
304 /* Dirty hack to make messages readable by Outlook Express
305 * for the Mac: force quotes around the boundary parameter
306 * even when they aren't needed.
309 if (!ascii_strcasecmp (p->attribute, "boundary")
310 && !strcmp (buffer, tmp))
311 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
315 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
317 if (len + tmplen + 2 > 76) {
326 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
334 fprintf (f, "Content-Description: %s\n", a->description);
336 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
339 if (!(fn = a->d_filename))
345 /* Strip off the leading path... */
346 if ((t = strrchr (fn, '/')))
353 encode = rfc2231_encode_string (&tmp);
354 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
356 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
362 if (a->encoding != ENC7BIT)
363 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
365 /* Do NOT add the terminator here!!! */
366 return (ferror (f) ? -1 : 0);
369 # define write_as_text_part(a) (mutt_is_text_part(a) \
370 || ((WithCrypto & APPLICATION_PGP)\
371 && mutt_is_application_pgp(a)))
373 int mutt_write_mime_body (BODY * a, FILE * f)
375 char *p, boundary[SHORT_STRING];
376 char send_charset[SHORT_STRING];
381 if (a->type == TYPEMULTIPART) {
382 /* First, find the boundary to use */
383 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
384 debug_print (1, ("no boundary parameter found!\n"));
385 mutt_error _("No boundary parameter found! [report this error]");
389 m_strcpy(boundary, sizeof(boundary), p);
391 for (t = a->parts; t; t = t->next) {
392 fprintf (f, "\n--%s\n", boundary);
393 if (mutt_write_mime_header (t, f) == -1)
396 if (mutt_write_mime_body (t, f) == -1)
399 fprintf (f, "\n--%s--\n", boundary);
400 return (ferror (f) ? -1 : 0);
403 /* This is pretty gross, but it's the best solution for now... */
404 if ((WithCrypto & APPLICATION_PGP)
405 && a->type == TYPEAPPLICATION
406 && m_strcmp(a->subtype, "pgp-encrypted") == 0) {
407 fputs ("Version: 1\n", f);
411 if ((fpin = fopen (a->filename, "r")) == NULL) {
412 debug_print (1, ("%s no longer exists!\n", a->filename));
413 mutt_error (_("%s no longer exists!"), a->filename);
417 if (a->type == TYPETEXT && (!a->noconv))
418 fc = fgetconv_open (fpin, a->file_charset,
419 mutt_get_body_charset (send_charset,
420 sizeof (send_charset), a), 0);
422 fc = fgetconv_open (fpin, 0, 0, 0);
424 if (a->encoding == ENCQUOTEDPRINTABLE)
425 encode_quoted (fc, f, write_as_text_part (a));
426 else if (a->encoding == ENCBASE64)
427 encode_base64 (fc, f, write_as_text_part (a));
428 else if (a->type == TYPETEXT && (!a->noconv))
429 encode_8bit (fc, f, write_as_text_part (a));
431 mutt_copy_stream (fpin, f);
433 fgetconv_close (&fc);
436 return (ferror (f) ? -1 : 0);
439 #undef write_as_text_part
441 #define BOUNDARYLEN 16
442 void mutt_generate_boundary (PARAMETER ** parm)
444 char rs[BOUNDARYLEN + 1];
449 for (i = 0; i < BOUNDARYLEN; i++)
450 *p++ = __m_b64chars[LRAND() % sizeof(__m_b64chars)];
453 mutt_set_parameter ("boundary", rs, parm);
465 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
469 int whitespace = s->whitespace;
471 int linelen = s->linelen;
472 int was_cr = s->was_cr;
474 if (!d) { /* This signals EOF */
477 if (linelen > info->linemax)
478 info->linemax = linelen;
483 for (; dlen; d++, dlen--) {
496 if (linelen > info->linemax)
497 info->linemax = linelen;
512 if (linelen > info->linemax)
513 info->linemax = linelen;
518 else if (ch == '\r') {
526 else if (ch == '\t' || ch == '\f') {
530 else if (ch < 32 || ch == 127)
534 if ((ch == 'F') || (ch == 'f'))
544 if (linelen == 2 && ch != 'r')
546 else if (linelen == 3 && ch != 'o')
548 else if (linelen == 4) {
561 if (ch != ' ' && ch != '\t')
566 s->whitespace = whitespace;
568 s->linelen = linelen;
573 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
574 #define BUGGY_ICONV 1
577 * Find the best charset conversion of the file from fromcode into one
578 * of the tocodes. If successful, set *tocode and CONTENT *info and
579 * return the number of characters converted inexactly. If no
580 * conversion was possible, return -1.
582 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
583 * which would otherwise prevent us from knowing the number of inexact
584 * conversions. Where the candidate target charset is UTF-8 we avoid
585 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
586 * fails with some libraries.
588 * We assume that the output from iconv is never more than 4 times as
589 * long as the input for any pair of charsets we might be interested
592 static size_t convert_file_to (FILE * file, const char *fromcode,
593 int ncodes, const char **tocodes,
594 int *tocode, CONTENT * info)
598 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
601 size_t ibl, obl, ubl, ubl1, n, ret;
604 CONTENT_STATE *states;
607 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
608 if (cd1 == (iconv_t) (-1))
611 cd = p_new(iconv_t, ncodes);
612 score = p_new(size_t, ncodes);
613 states = p_new(CONTENT_STATE, ncodes);
614 infos = p_new(CONTENT, ncodes);
616 for (i = 0; i < ncodes; i++)
617 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
618 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
620 /* Special case for conversion to UTF-8 */
621 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
627 /* Try to fill input buffer */
628 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
631 /* Convert to UTF-8 */
633 ob = bufu, obl = sizeof (bufu);
634 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
635 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
636 if (n == (size_t) (-1) &&
637 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
638 assert (errno == EILSEQ ||
639 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
645 /* Convert from UTF-8 */
646 for (i = 0; i < ncodes; i++)
647 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
648 ub = bufu, ubl = ubl1;
649 ob = bufo, obl = sizeof (bufo);
650 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
651 if (n == (size_t) (-1)) {
652 assert (errno == E2BIG ||
653 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
654 score[i] = (size_t) (-1);
658 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
661 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
662 /* Special case for conversion to UTF-8 */
663 update_content_info (&infos[i], &states[i], bufu, ubl1);
666 /* Save unused input */
667 memmove (bufi, ib, ibl);
668 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
675 /* Find best score */
677 for (i = 0; i < ncodes; i++) {
678 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
679 /* Special case for conversion to UTF-8 */
684 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
686 else if (ret == (size_t) (-1) || score[i] < ret) {
693 if (ret != (size_t) (-1)) {
694 memcpy (info, &infos[*tocode], sizeof (CONTENT));
695 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
699 for (i = 0; i < ncodes; i++)
700 if (cd[i] != (iconv_t) (-1))
712 #endif /* !HAVE_ICONV */
716 * Find the first of the fromcodes that gives a valid conversion and
717 * the best charset conversion of the file into one of the tocodes. If
718 * successful, set *fromcode and *tocode to dynamically allocated
719 * strings, set CONTENT *info, and return the number of characters
720 * converted inexactly. If no conversion was possible, return -1.
722 * Both fromcodes and tocodes may be colon-separated lists of charsets.
723 * However, if fromcode is zero then fromcodes is assumed to be the
724 * name of a single charset even if it contains a colon.
726 static size_t convert_file_from_to (FILE * file,
727 const char *fromcodes,
728 const char *tocodes, char **fromcode,
729 char **tocode, CONTENT * info)
737 /* Count the tocodes */
739 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
740 if ((c1 = strchr (c, ':')) == c)
746 tcode = p_new(char *, ncodes);
747 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
748 if ((c1 = strchr (c, ':')) == c)
750 tcode[i] = str_substrdup (c, c1);
755 /* Try each fromcode in turn */
756 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
757 if ((c1 = strchr (c, ':')) == c)
759 fcode = str_substrdup (c, c1);
761 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
763 if (ret != (size_t) (-1)) {
773 /* There is only one fromcode */
774 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
776 if (ret != (size_t) (-1)) {
783 for (i = 0; i < ncodes; i++)
792 * Analyze the contents of a file to determine which MIME encoding to use.
793 * Also set the body charset, sometimes, or not.
795 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
800 char *fromcode = NULL;
811 if (stat (fname, &sb) == -1) {
812 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
816 if (!S_ISREG (sb.st_mode)) {
817 mutt_error (_("%s isn't a regular file."), fname);
821 if ((fp = fopen (fname, "r")) == NULL) {
822 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
826 info = p_new(CONTENT, 1);
829 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
830 char *chs = mutt_get_parameter ("charset", b->parameter);
831 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
832 FileCharset : Charset) : Charset;
833 if (Charset && (chs || SendCharset) &&
834 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
835 &fromcode, &tocode, info) != (size_t) (-1)) {
837 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
838 mutt_set_parameter ("charset", chsbuf, &b->parameter);
840 b->file_charset = fromcode;
848 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
849 update_content_info (info, &state, buffer, r);
850 update_content_info (info, &state, 0, 0);
854 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
855 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
857 && !mutt_is_us_ascii (Charset) ? Charset :
858 "unknown-8bit"), &b->parameter);
863 /* Given a file with path ``s'', see if there is a registered MIME type.
864 * returns the major MIME type, and copies the subtype to ``d''. First look
865 * for ~/.mime.types, then look in a system mime.types if we can find one.
866 * The longest match is used so that we can match `ps.gz' when `gz' also
870 int mutt_lookup_mime_type (BODY * att, const char *path)
874 char buf[LONG_STRING];
875 char subtype[STRING], xtype[STRING];
877 int szf, sze, cur_sze;
885 szf = m_strlen(path);
887 for (count = 0; count < 4; count++) {
889 * can't use strtok() because we use it in an inner loop below, so use
890 * a switch statement here instead.
894 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
897 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/muttng-mime.types");
900 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
903 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
906 debug_print (1, ("Internal error, count = %d.\n", count));
907 goto bye; /* shouldn't happen */
910 if ((f = fopen (buf, "r")) != NULL) {
911 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
912 /* weed out any comments */
913 if ((p = strchr (buf, '#')))
916 /* remove any leading space. */
917 ct = vskipspaces(buf);
919 /* position on the next field in this line */
920 if ((p = strpbrk (ct, " \t")) == NULL)
925 /* cycle through the file extensions */
926 while ((p = strtok (p, " \t\n"))) {
928 if ((sze > cur_sze) && (szf >= sze) &&
929 (m_strcasecmp(path + szf - sze, p) == 0
930 || ascii_strcasecmp (path + szf - sze, p) == 0)
931 && (szf == sze || path[szf - sze - 1] == '.'))
933 /* get the content-type */
935 if ((p = strchr (ct, '/')) == NULL) {
936 /* malformed line, just skip it. */
941 for (q = p; *q && !ISSPACE (*q); q++);
943 str_substrcpy (subtype, p, q, sizeof (subtype));
945 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
946 m_strcpy(xtype, sizeof(xtype), ct);
959 if (type != TYPEOTHER || *xtype != '\0') {
961 str_replace (&att->subtype, subtype);
962 str_replace (&att->xtype, xtype);
968 void mutt_message_to_7bit (BODY * a, FILE * fp)
970 char temp[_POSIX_PATH_MAX];
976 if (!a->filename && fp)
978 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
979 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
984 if (stat (a->filename, &sb) == -1) {
985 mutt_perror ("stat");
988 a->length = sb.st_size;
992 if (!(fpout = safe_fopen (temp, "w+"))) {
993 mutt_perror ("fopen");
997 fseeko (fpin, a->offset, 0);
998 a->parts = mutt_parse_messageRFC822 (fpin, a);
1000 transform_to_7bit (a->parts, fpin);
1002 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1003 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1005 fputs ("MIME-Version: 1.0\n", fpout);
1006 mutt_write_mime_header (a->parts, fpout);
1007 fputc ('\n', fpout);
1008 mutt_write_mime_body (a->parts, fpout);
1020 a->encoding = ENC7BIT;
1021 a->d_filename = a->filename;
1022 if (a->filename && a->unlink)
1023 unlink (a->filename);
1024 a->filename = m_strdup(temp);
1026 if (stat (a->filename, &sb) == -1) {
1027 mutt_perror ("stat");
1030 a->length = sb.st_size;
1031 mutt_free_body (&a->parts);
1032 a->hdr->content = NULL;
1035 static void transform_to_7bit (BODY * a, FILE * fpin)
1037 char buff[_POSIX_PATH_MAX];
1042 for (; a; a = a->next) {
1043 if (a->type == TYPEMULTIPART) {
1044 if (a->encoding != ENC7BIT)
1045 a->encoding = ENC7BIT;
1047 transform_to_7bit (a->parts, fpin);
1049 else if (mutt_is_message_type (a->type, a->subtype)) {
1050 mutt_message_to_7bit (a, fpin);
1054 a->force_charset = 1;
1057 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1058 mutt_perror ("fopen");
1062 mutt_decode_attachment (a, &s);
1064 a->d_filename = a->filename;
1065 a->filename = m_strdup(buff);
1067 if (stat (a->filename, &sb) == -1) {
1068 mutt_perror ("stat");
1071 a->length = sb.st_size;
1073 mutt_update_encoding (a);
1074 if (a->encoding == ENC8BIT)
1075 a->encoding = ENCQUOTEDPRINTABLE;
1076 else if (a->encoding == ENCBINARY)
1077 a->encoding = ENCBASE64;
1082 /* determine which Content-Transfer-Encoding to use */
1083 static void mutt_set_encoding (BODY * b, CONTENT * info)
1085 char send_charset[SHORT_STRING];
1087 if (b->type == TYPETEXT) {
1089 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1090 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1091 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1092 b->encoding = ENCQUOTEDPRINTABLE;
1093 else if (info->hibin)
1094 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1096 b->encoding = ENC7BIT;
1098 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1099 if (info->lobin || info->hibin) {
1100 if (option (OPTALLOW8BIT) && !info->lobin)
1101 b->encoding = ENC8BIT;
1103 mutt_message_to_7bit (b, NULL);
1106 b->encoding = ENC7BIT;
1108 else if (b->type == TYPEAPPLICATION
1109 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1110 b->encoding = ENC7BIT;
1113 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1114 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1117 /* Determine which encoding is smaller */
1118 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1119 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1120 b->encoding = ENCBASE64;
1122 b->encoding = ENCQUOTEDPRINTABLE;
1126 b->encoding = ENC7BIT;
1130 void mutt_stamp_attachment (BODY * a)
1132 a->stamp = time (NULL);
1135 /* Get a body's character set */
1137 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1141 if (b && b->type != TYPETEXT)
1145 p = mutt_get_parameter ("charset", b->parameter);
1148 mutt_canonical_charset (d, dlen, NONULL (p));
1150 m_strcpy(d, dlen, "us-ascii");
1156 /* Assumes called from send mode where BODY->filename points to actual file */
1157 void mutt_update_encoding (BODY * a)
1160 char chsbuff[STRING];
1162 /* override noconv when it's us-ascii */
1163 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1166 if (!a->force_charset && !a->noconv)
1167 mutt_delete_parameter ("charset", &a->parameter);
1169 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1172 mutt_set_encoding (a, info);
1173 mutt_stamp_attachment (a);
1175 p_delete(&a->content);
1180 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1182 char buffer[LONG_STRING];
1185 int cmflags, chflags;
1186 int pgp = WithCrypto ? hdr->security : 0;
1189 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1190 (hdr->security & ENCRYPT)) {
1191 if (!crypt_valid_passphrase (hdr->security))
1196 mutt_mktemp (buffer);
1197 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1200 body = mutt_new_body ();
1201 body->type = TYPEMESSAGE;
1202 body->subtype = m_strdup("rfc822");
1203 body->filename = m_strdup(buffer);
1206 body->disposition = DISPINLINE;
1209 mutt_parse_mime_message (ctx, hdr);
1214 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1215 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1216 chflags |= CH_MIME | CH_TXTPLAIN;
1217 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1218 if ((WithCrypto & APPLICATION_PGP))
1220 if ((WithCrypto & APPLICATION_SMIME))
1221 pgp &= ~SMIMEENCRYPT;
1223 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1224 if ((WithCrypto & APPLICATION_PGP)
1225 && mutt_is_multipart_encrypted (hdr->content)) {
1226 chflags |= CH_MIME | CH_NONEWLINE;
1227 cmflags = M_CM_DECODE_PGP;
1230 else if ((WithCrypto & APPLICATION_PGP)
1231 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1232 chflags |= CH_MIME | CH_TXTPLAIN;
1233 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1236 else if ((WithCrypto & APPLICATION_SMIME)
1237 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1238 chflags |= CH_MIME | CH_TXTPLAIN;
1239 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1240 pgp &= ~SMIMEENCRYPT;
1244 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1249 body->hdr = mutt_new_header ();
1250 body->hdr->offset = 0;
1251 /* we don't need the user headers here */
1252 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1254 body->hdr->security = pgp;
1255 mutt_update_encoding (body);
1256 body->parts = body->hdr->content;
1263 BODY *mutt_make_file_attach (const char *path)
1268 att = mutt_new_body ();
1269 att->filename = m_strdup(path);
1271 /* Attempt to determine the appropriate content-type based on the filename
1278 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1279 path)) != TYPEOTHER || *xbuf != '\0') {
1281 att->subtype = m_strdup(buf);
1282 att->xtype = m_strdup(xbuf);
1287 mutt_lookup_mime_type (att, path);
1291 if ((info = mutt_get_content_info (path, att)) == NULL) {
1292 mutt_free_body (&att);
1296 if (!att->subtype) {
1297 if (info->lobin == 0
1298 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1300 * Statistically speaking, there should be more than 10% "lobin"
1301 * chars if this is really a binary file...
1303 att->type = TYPETEXT;
1304 att->subtype = m_strdup("plain");
1307 att->type = TYPEAPPLICATION;
1308 att->subtype = m_strdup("octet-stream");
1312 mutt_update_encoding (att);
1316 static int get_toplevel_encoding (BODY * a)
1320 for (; a; a = a->next) {
1321 if (a->encoding == ENCBINARY)
1323 else if (a->encoding == ENC8BIT)
1330 BODY *mutt_make_multipart (BODY * b)
1334 new = mutt_new_body ();
1335 new->type = TYPEMULTIPART;
1336 new->subtype = m_strdup("mixed");
1337 new->encoding = get_toplevel_encoding (b);
1338 mutt_generate_boundary (&new->parameter);
1340 new->disposition = DISPINLINE;
1346 /* remove the multipart body if it exists */
1347 BODY *mutt_remove_multipart (BODY * b)
1355 mutt_free_body (&t);
1360 char *mutt_make_date (char *s, size_t len)
1362 time_t t = time (NULL);
1363 struct tm *l = localtime (&t);
1364 time_t tz = mutt_local_tz (t);
1368 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1369 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1370 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1371 (int) tz / 60, (int) abs (tz) % 60);
1375 /* wrapper around mutt_write_address() so we can handle very large
1376 recipient lists without needing a huge temporary buffer in memory */
1377 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1381 char buf[LONG_STRING];
1389 rfc822_write_address (buf, sizeof (buf), adr, display);
1390 len = m_strlen(buf);
1391 if (count && linelen + len > 74) {
1393 linelen = len + 8; /* tab is usually about 8 spaces... */
1396 if (count && adr->mailbox) {
1404 if (!adr->group && adr->next && adr->next->mailbox) {
1414 /* arbitrary number of elements to grow the array by */
1419 /* need to write the list in reverse because they are stored in reverse order
1420 * when parsed to speed up threading
1422 void mutt_write_references (LIST * r, FILE * f)
1425 int refcnt = 0, refmax = 0;
1427 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1428 if (refcnt == refmax)
1429 p_realloc(&ref, refmax += REF_INC);
1433 while (refcnt-- > 0) {
1435 fputs (ref[refcnt]->data, f);
1441 /* Note: all RFC2047 encoding should be done outside of this routine, except
1442 * for the "real name." This will allow this routine to be used more than
1443 * once, if necessary.
1445 * Likewise, all IDN processing should happen outside of this routine.
1447 * mode == 1 => "lite" mode (used for edit_hdrs)
1448 * mode == 0 => normal mode. write full header + MIME headers
1449 * mode == -1 => write just the envelope info (used for postponing messages)
1451 * privacy != 0 => will omit any headers which may identify the user.
1452 * Output generated is suitable for being sent through
1453 * anonymous remailer chains.
1457 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1458 int mode, int privacy)
1460 char buffer[LONG_STRING];
1462 LIST *tmp = env->userhdrs;
1463 int has_agent = 0; /* user defined user-agent header field exists */
1464 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1467 if (!option (OPTNEWSSEND))
1469 if (mode == 0 && !privacy)
1470 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1472 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1474 /* OPTUSEFROM is not consulted here so that we can still write a From:
1475 * field if the user sets it with the `my_hdr' command
1477 if (env->from && !privacy) {
1479 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1480 fprintf (fp, "From: %s\n", buffer);
1485 mutt_write_address_list (env->to, fp, 4, 0);
1489 if (!option (OPTNEWSSEND))
1491 if (EDIT_HEADER("To:"))
1492 fputs ("To:\n", fp);
1496 mutt_write_address_list (env->cc, fp, 4, 0);
1500 if (!option (OPTNEWSSEND))
1502 if (EDIT_HEADER("Cc:"))
1503 fputs ("Cc:\n", fp);
1506 if (mode != 0 || option (OPTWRITEBCC)) {
1507 fputs ("Bcc: ", fp);
1508 mutt_write_address_list (env->bcc, fp, 5, 0);
1513 if (!option (OPTNEWSSEND))
1515 if (EDIT_HEADER("Bcc:"))
1516 fputs ("Bcc:\n", fp);
1519 if (env->newsgroups)
1520 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1521 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1522 fputs ("Newsgroups:\n", fp);
1524 if (env->followup_to)
1525 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1526 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1527 fputs ("Followup-To:\n", fp);
1529 if (env->x_comment_to)
1530 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1531 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1532 EDIT_HEADER("X-Comment-To:"))
1533 fputs ("X-Comment-To:\n", fp);
1537 fprintf (fp, "Subject: %s\n", env->subject);
1538 else if (mode == 1 && EDIT_HEADER("Subject:"))
1539 fputs ("Subject:\n", fp);
1541 /* save message id if the user has set it */
1542 if (env->message_id && !privacy)
1543 fprintf (fp, "Message-ID: %s\n", env->message_id);
1545 if (env->reply_to) {
1546 fputs ("Reply-To: ", fp);
1547 mutt_write_address_list (env->reply_to, fp, 10, 0);
1549 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1550 fputs ("Reply-To:\n", fp);
1552 if (env->mail_followup_to)
1554 if (!option (OPTNEWSSEND))
1557 fputs ("Mail-Followup-To: ", fp);
1558 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1562 if (env->references) {
1563 fputs ("References:", fp);
1564 mutt_write_references (env->references, fp);
1568 /* Add the MIME headers */
1569 fputs ("MIME-Version: 1.0\n", fp);
1570 mutt_write_mime_header (attach, fp);
1573 if (env->in_reply_to) {
1574 fputs ("In-Reply-To:", fp);
1575 mutt_write_references (env->in_reply_to, fp);
1581 /* Add any user defined headers */
1582 for (; tmp; tmp = tmp->next) {
1583 if ((p = strchr (tmp->data, ':'))) {
1584 p = vskipspaces(p + 1);
1586 continue; /* don't emit empty fields. */
1588 /* check to see if the user has overridden the user-agent field */
1589 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1595 fputs (tmp->data, fp);
1600 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1603 if (OperatingSystem != NULL) {
1604 os = OperatingSystem;
1607 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1609 /* Add a vanity header */
1610 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1613 list_del (&hdrs, (list_del_t*)xmemfree);
1615 return (ferror (fp) == 0 ? 0 : -1);
1618 static void encode_headers (LIST * h)
1624 for (; h; h = h->next) {
1625 if (!(p = strchr (h->data, ':')))
1629 p = vskipspaces(p + 1);
1635 rfc2047_encode_string (&tmp);
1636 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1638 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1644 const char *mutt_fqdn (short may_hide_host)
1648 if (Fqdn && Fqdn[0] != '@') {
1651 if (may_hide_host && option (OPTHIDDENHOST)) {
1652 if ((p = strchr (Fqdn, '.')))
1655 /* sanity check: don't hide the host if
1656 * the fqdn is something like detebe.org.
1659 if (!p || !(q = strchr (p, '.')))
1667 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1668 static char mutt_normalized_char(char c)
1670 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1673 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1675 #define APPEND_FMT(fmt, arg) \
1677 int snlen = snprintf(buf, len, fmt, arg); \
1682 #define APPEND_BYTE(c) \
1699 APPEND_BYTE(mutt_normalized_char(c));
1707 APPEND_FMT("%02d", tm->tm_mday);
1710 APPEND_FMT("%02d", tm->tm_hour);
1713 APPEND_FMT("%02d", tm->tm_mon + 1);
1716 APPEND_FMT("%02d", tm->tm_min);
1719 APPEND_FMT("%lo", (unsigned long)now);
1722 APPEND_FMT("%u", (unsigned int)getpid());
1725 APPEND_FMT("%c", MsgIdPfx);
1726 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1729 APPEND_FMT("%u", (unsigned int)rand());
1732 APPEND_FMT("%x", (unsigned int)rand());
1735 APPEND_FMT("%02d", tm->tm_sec);
1738 APPEND_FMT("%u", (unsigned int) now);
1741 APPEND_FMT("%x", (unsigned int) now);
1743 case 'Y': /* this will break in the year 10000 ;-) */
1744 APPEND_FMT("%04d", tm->tm_year + 1900);
1749 default: /* invalid formats are replaced by '.' */
1751 m_strncat(buf, len, ".", 1);
1761 char *mutt_gen_msgid (void)
1763 char buf[SHORT_STRING];
1764 char localpart[SHORT_STRING];
1765 unsigned int localpart_length;
1768 if (!(fqdn = mutt_fqdn (0)))
1769 fqdn = NONULL (Hostname);
1771 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1773 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1775 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1776 return (m_strdup(buf));
1779 static RETSIGTYPE alarm_handler (int sig)
1784 /* invoke sendmail in a subshell
1785 path (in) path to program to execute
1786 args (in) arguments to pass to program
1787 msg (in) temp file containing message to send
1788 tempfile (out) if sendmail is put in the background, this points
1789 to the temporary file containing the stdout of the
1792 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1798 mutt_block_signals_system ();
1801 /* we also don't want to be stopped right now */
1802 sigaddset (&set, SIGTSTP);
1803 sigprocmask (SIG_BLOCK, &set, NULL);
1805 if (SendmailWait >= 0) {
1806 char tmp[_POSIX_PATH_MAX];
1809 *tempfile = m_strdup(tmp);
1812 if ((pid = fork ()) == 0) {
1813 struct sigaction act, oldalrm;
1815 /* save parent's ID before setsid() */
1818 /* we want the delivery to continue even after the main process dies,
1819 * so we put ourselves into another session right away
1823 /* next we close all open files */
1824 #if defined(OPEN_MAX)
1825 for (fd = 0; fd < OPEN_MAX; fd++)
1827 #elif defined(_POSIX_OPEN_MAX)
1828 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1836 /* now the second fork() */
1837 if ((pid = fork ()) == 0) {
1838 /* "msg" will be opened as stdin */
1839 if (open (msg, O_RDONLY, 0) < 0) {
1845 if (SendmailWait >= 0) {
1846 /* *tempfile will be opened as stdout */
1847 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1850 /* redirect stderr to *tempfile too */
1855 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1857 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1864 else if (pid == -1) {
1870 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1871 * SendmailWait = 0: wait forever
1872 * SendmailWait < 0: don't wait
1874 if (SendmailWait > 0) {
1876 act.sa_handler = alarm_handler;
1878 /* need to make sure waitpid() is interrupted on SIGALRM */
1879 act.sa_flags = SA_INTERRUPT;
1883 sigemptyset (&act.sa_mask);
1884 sigaction (SIGALRM, &act, &oldalrm);
1885 alarm (SendmailWait);
1887 else if (SendmailWait < 0)
1888 _exit (0xff & EX_OK);
1890 if (waitpid (pid, &st, 0) > 0) {
1891 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1892 if (SendmailWait && st == (0xff & EX_OK)) {
1893 unlink (*tempfile); /* no longer needed */
1898 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1899 if (SendmailWait > 0) {
1905 /* reset alarm; not really needed, but... */
1907 sigaction (SIGALRM, &oldalrm, NULL);
1909 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1910 /* the parent is already dead */
1918 sigprocmask (SIG_UNBLOCK, &set, NULL);
1920 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1921 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1923 st = S_ERR; /* error */
1925 mutt_unblock_signals_system (1);
1930 static const char **
1931 add_args(const char **args, size_t *argslen, size_t *argsmax, ADDRESS * addr)
1933 for (; addr; addr = addr->next) {
1934 /* weed out group mailboxes, since those are for display only */
1935 if (addr->mailbox && !addr->group) {
1936 if (*argslen == *argsmax)
1937 p_realloc(&args, *argsmax += 5);
1938 args[(*argslen)++] = addr->mailbox;
1944 static const char **
1945 add_option(const char **args, size_t *argslen, size_t *argsmax, const char *s)
1947 if (*argslen == *argsmax) {
1948 p_realloc(&args, *argsmax += 5);
1950 args[(*argslen)++] = s;
1954 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
1955 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
1956 const char *msg, /* file containing message */
1958 { /* message contains 8bit chars */
1959 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1960 const char **args = NULL;
1961 size_t argslen = 0, argsmax = 0;
1965 if (option (OPTNEWSSEND)) {
1966 char cmd[LONG_STRING];
1968 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1971 i = nntp_post (msg);
1980 s = m_strdup(Sendmail);
1984 while ((ps = strtok (ps, " "))) {
1985 if (argslen == argsmax)
1986 p_realloc(&args, argsmax += 5);
1989 args[argslen++] = ps;
1991 path = m_strdup(ps);
1992 ps = strrchr (ps, '/');
1997 args[argslen++] = ps;
2004 if (!option (OPTNEWSSEND)) {
2006 if (eightbit && option (OPTUSE8BITMIME))
2007 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
2009 if (option (OPTENVFROM)) {
2013 else if (from && !from->next)
2016 args = add_option (args, &argslen, &argsmax, "-f");
2017 args = add_args (args, &argslen, &argsmax, f);
2021 args = add_option (args, &argslen, &argsmax, "-N");
2022 args = add_option (args, &argslen, &argsmax, DsnNotify);
2025 args = add_option (args, &argslen, &argsmax, "-R");
2026 args = add_option (args, &argslen, &argsmax, DsnReturn);
2028 args = add_option (args, &argslen, &argsmax, "--");
2029 args = add_args (args, &argslen, &argsmax, to);
2030 args = add_args (args, &argslen, &argsmax, cc);
2031 args = add_args (args, &argslen, &argsmax, bcc);
2036 if (argslen == argsmax)
2037 p_realloc(&args, ++argsmax);
2039 args[argslen++] = NULL;
2041 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2043 const char *e = mutt_strsysexit (i);
2045 e = mutt_strsysexit (i);
2046 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2051 if (stat (childout, &st) == 0 && st.st_size > 0)
2052 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2060 p_delete(&childout);
2065 if (i == (EX_OK & 0xff))
2067 else if (i == S_BKG)
2074 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2075 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2076 const char *msg, /* file containing message */
2078 { /* message contains 8bit chars */
2081 if (!option (OPTNEWSSEND))
2084 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2087 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2090 /* appends string 'b' to string 'a', and returns the pointer to the new
2092 char *mutt_append_string (char *a, const char *b)
2094 size_t la = m_strlen(a);
2096 p_realloc(&a, la + m_strlen(b) + 1);
2097 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2101 /* returns 1 if char `c' needs to be quoted to protect from shell
2102 interpretation when executing commands in a subshell */
2103 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2105 /* returns 1 if string `s' contains characters which could cause problems
2106 when used on a command line to execute a command */
2107 int mutt_needs_quote (const char *s)
2110 if (INVALID_CHAR (*s))
2117 /* Quote a string to prevent shell escapes when this string is used on the
2118 command line to send mail. */
2119 char *mutt_quote_string (const char *s)
2124 rlen = m_strlen(s) + 3;
2125 pr = r = p_new(char, rlen);
2128 if (INVALID_CHAR (*s)) {
2131 p_realloc(&r, ++rlen);
2142 /* For postponing (!final) do the necessary encodings only */
2143 void mutt_prepare_envelope (ENVELOPE * env, int final)
2145 char buffer[LONG_STRING];
2148 if (env->bcc && !(env->to || env->cc)) {
2149 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2150 * recipients if there is no To: or Cc: field, so attempt to suppress
2151 * it by using an empty To: field.
2153 env->to = rfc822_new_address ();
2155 env->to->next = rfc822_new_address ();
2158 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2161 env->to->mailbox = m_strdup(buffer);
2164 mutt_set_followup_to (env);
2166 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2167 env->message_id = mutt_gen_msgid ();
2170 /* Take care of 8-bit => 7-bit conversion. */
2171 rfc2047_encode_adrlist (env->to, "To");
2172 rfc2047_encode_adrlist (env->cc, "Cc");
2173 rfc2047_encode_adrlist (env->bcc, "Bcc");
2174 rfc2047_encode_adrlist (env->from, "From");
2175 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2176 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2180 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2183 rfc2047_encode_string (&env->subject);
2185 encode_headers (env->userhdrs);
2188 void mutt_unprepare_envelope (ENVELOPE * env)
2192 for (item = env->userhdrs; item; item = item->next)
2193 rfc2047_decode (&item->data);
2195 rfc822_free_address (&env->mail_followup_to);
2197 /* back conversions */
2198 rfc2047_decode_adrlist (env->to);
2199 rfc2047_decode_adrlist (env->cc);
2200 rfc2047_decode_adrlist (env->bcc);
2201 rfc2047_decode_adrlist (env->from);
2202 rfc2047_decode_adrlist (env->reply_to);
2203 rfc2047_decode (&env->subject);
2206 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2207 const char *resent_from, ADDRESS * env_from)
2211 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2212 MESSAGE *msg = NULL;
2215 /* Try to bounce each message out, aborting if we get any failures. */
2216 for (i = 0; i < Context->msgcount; i++)
2217 if (Context->hdrs[i]->tagged)
2219 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2224 /* If we failed to open a message, return with error */
2225 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2231 mutt_mktemp (tempfile);
2232 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2233 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2235 if (!option (OPTBOUNCEDELIVERED))
2236 ch_flags |= CH_WEED_DELIVERED;
2238 fseeko (fp, h->offset, 0);
2239 fprintf (f, "Resent-From: %s", resent_from);
2240 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2241 if (MsgIdFormat && *MsgIdFormat)
2242 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2243 fputs ("Resent-To: ", f);
2244 mutt_write_address_list (to, f, 11, 0);
2245 mutt_copy_header (fp, h, f, ch_flags, NULL);
2247 mutt_copy_bytes (fp, f, h->content->length);
2250 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2251 h->content->encoding == ENC8BIT);
2255 mx_close_message (&msg);
2260 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2263 const char *fqdn = mutt_fqdn (1);
2264 char resent_from[STRING];
2268 resent_from[0] = '\0';
2269 from = mutt_default_from ();
2272 rfc822_qualify (from, fqdn);
2274 rfc2047_encode_adrlist (from, "Resent-From");
2275 if (mutt_addrlist_to_idna (from, &err)) {
2276 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2279 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2282 unset_option (OPTNEWSSEND);
2285 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2287 rfc822_free_address (&from);
2293 /* given a list of addresses, return a list of unique addresses */
2294 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2296 ADDRESS *top = addr;
2297 ADDRESS **last = ⊤
2302 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2303 if (tmp->mailbox && addr->mailbox &&
2304 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2311 debug_print (2, ("Removing %s\n", addr->mailbox));
2316 rfc822_free_address (&addr);
2329 static void set_noconv_flags (BODY * b, short flag)
2331 for (; b; b = b->next) {
2332 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2333 set_noconv_flags (b->parts, flag);
2334 else if (b->type == TYPETEXT && b->noconv) {
2336 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2338 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2343 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2344 int post, char *fcc)
2348 char tempfile[_POSIX_PATH_MAX];
2349 FILE *tempfp = NULL;
2353 set_noconv_flags (hdr->content, 1);
2355 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2356 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2360 /* We need to add a Content-Length field to avoid problems where a line in
2361 * the message body begins with "From "
2363 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2364 mutt_mktemp (tempfile);
2365 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2366 mutt_perror (tempfile);
2367 mx_close_mailbox (&f, NULL);
2372 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2373 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2374 mx_close_mailbox (&f, NULL);
2378 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2379 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2381 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2384 /* (postponment) if this was a reply of some sort, <msgid> contians the
2385 * Message-ID: of message replied to. Save it using a special X-Mutt-
2386 * header so it can be picked up if the message is recalled at a later
2387 * point in time. This will allow the message to be marked as replied if
2388 * the same mailbox is still open.
2391 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2393 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2394 * it can be picked up when the message is recalled
2397 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2398 fprintf (msg->fp, "Status: RO\n");
2402 /* (postponment) if the mail is to be signed or encrypted, save this info */
2403 if ((WithCrypto & APPLICATION_PGP)
2404 && post && (hdr->security & APPLICATION_PGP)) {
2405 fputs ("X-Mutt-PGP: ", msg->fp);
2406 if (hdr->security & ENCRYPT)
2407 fputc ('E', msg->fp);
2408 if (hdr->security & SIGN) {
2409 fputc ('S', msg->fp);
2410 if (PgpSignAs && *PgpSignAs)
2411 fprintf (msg->fp, "<%s>", PgpSignAs);
2413 if (hdr->security & INLINE)
2414 fputc ('I', msg->fp);
2415 fputc ('\n', msg->fp);
2418 /* (postponment) if the mail is to be signed or encrypted, save this info */
2419 if ((WithCrypto & APPLICATION_SMIME)
2420 && post && (hdr->security & APPLICATION_SMIME)) {
2421 fputs ("X-Mutt-SMIME: ", msg->fp);
2422 if (hdr->security & ENCRYPT) {
2423 fputc ('E', msg->fp);
2424 if (SmimeCryptAlg && *SmimeCryptAlg)
2425 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2427 if (hdr->security & SIGN) {
2428 fputc ('S', msg->fp);
2429 if (SmimeDefaultKey && *SmimeDefaultKey)
2430 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2432 if (hdr->security & INLINE)
2433 fputc ('I', msg->fp);
2434 fputc ('\n', msg->fp);
2438 /* (postponement) if the mail is to be sent through a mixmaster
2439 * chain, save that information
2442 if (post && hdr->chain && hdr->chain) {
2445 fputs ("X-Mutt-Mix:", msg->fp);
2446 for (p = hdr->chain; p; p = p->next)
2447 fprintf (msg->fp, " %s", (char *) p->data);
2449 fputc ('\n', msg->fp);
2454 char sasha[LONG_STRING];
2457 mutt_write_mime_body (hdr->content, tempfp);
2459 /* make sure the last line ends with a newline. Emacs doesn't ensure
2460 * this will happen, and it can cause problems parsing the mailbox
2463 fseeko (tempfp, -1, 2);
2464 if (fgetc (tempfp) != '\n') {
2465 fseeko (tempfp, 0, 2);
2466 fputc ('\n', tempfp);
2470 if (ferror (tempfp)) {
2471 debug_print (1, ("%s: write failed.\n", tempfile));
2474 mx_commit_message (msg, &f); /* XXX - really? */
2475 mx_close_message (&msg);
2476 mx_close_mailbox (&f, NULL);
2480 /* count the number of lines */
2482 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2484 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2485 fprintf (msg->fp, "Lines: %d\n\n", lines);
2487 /* copy the body and clean up */
2489 r = mutt_copy_stream (tempfp, msg->fp);
2490 if (fclose (tempfp) != 0)
2492 /* if there was an error, leave the temp version */
2497 fputc ('\n', msg->fp); /* finish off the header */
2498 r = mutt_write_mime_body (hdr->content, msg->fp);
2501 if (mx_commit_message (msg, &f) != 0)
2503 mx_close_message (&msg);
2504 mx_close_mailbox (&f, NULL);
2507 set_noconv_flags (hdr->content, 0);