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.
25 #include <sys/utsname.h>
27 #include <lib-lib/mem.h>
28 #include <lib-lib/ascii.h>
29 #include <lib-lib/str.h>
30 #include <lib-lib/macros.h>
31 #include <lib-lib/file.h>
32 #include <lib-lib/debug.h>
34 #include <lib-sys/exit.h>
35 #include <lib-sys/mutt_signal.h>
37 #include <lib-mime/mime.h>
39 #include <lib-ui/curses.h>
43 #include "recvattach.h"
48 #include <lib-crypt/crypt.h>
49 #include "mutt_idna.h"
52 # include "mutt_libesmtp.h"
53 #endif /* USE_LIBESMTP */
59 #ifdef HAVE_SYSEXITS_H
61 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
65 /* If you are debugging this file, comment out the following line. */
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_strcpy(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_strcpy(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) || mutt_is_application_pgp(a))
371 int mutt_write_mime_body (BODY * a, FILE * f)
373 char *p, boundary[SHORT_STRING];
374 char send_charset[SHORT_STRING];
379 if (a->type == TYPEMULTIPART) {
380 /* First, find the boundary to use */
381 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
382 debug_print (1, ("no boundary parameter found!\n"));
383 mutt_error _("No boundary parameter found! [report this error]");
387 m_strcpy(boundary, sizeof(boundary), p);
389 for (t = a->parts; t; t = t->next) {
390 fprintf (f, "\n--%s\n", boundary);
391 if (mutt_write_mime_header (t, f) == -1)
394 if (mutt_write_mime_body (t, f) == -1)
397 fprintf (f, "\n--%s--\n", boundary);
398 return (ferror (f) ? -1 : 0);
401 /* This is pretty gross, but it's the best solution for now... */
402 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
403 fputs ("Version: 1\n", f);
407 if ((fpin = fopen (a->filename, "r")) == NULL) {
408 debug_print (1, ("%s no longer exists!\n", a->filename));
409 mutt_error (_("%s no longer exists!"), a->filename);
413 if (a->type == TYPETEXT && (!a->noconv))
414 fc = fgetconv_open (fpin, a->file_charset,
415 mutt_get_body_charset (send_charset,
416 sizeof (send_charset), a), 0);
418 fc = fgetconv_open (fpin, 0, 0, 0);
420 if (a->encoding == ENCQUOTEDPRINTABLE)
421 encode_quoted (fc, f, write_as_text_part (a));
422 else if (a->encoding == ENCBASE64)
423 encode_base64 (fc, f, write_as_text_part (a));
424 else if (a->type == TYPETEXT && (!a->noconv))
425 encode_8bit (fc, f, write_as_text_part (a));
427 mutt_copy_stream (fpin, f);
429 fgetconv_close (&fc);
432 return (ferror (f) ? -1 : 0);
435 #undef write_as_text_part
437 #define BOUNDARYLEN 16
438 void mutt_generate_boundary (PARAMETER ** parm)
440 char rs[BOUNDARYLEN + 1];
445 for (i = 0; i < BOUNDARYLEN; i++)
446 *p++ = __m_b64chars[LRAND() % sizeof(__m_b64chars)];
449 mutt_set_parameter ("boundary", rs, parm);
461 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
465 int whitespace = s->whitespace;
467 int linelen = s->linelen;
468 int was_cr = s->was_cr;
470 if (!d) { /* This signals EOF */
473 if (linelen > info->linemax)
474 info->linemax = linelen;
479 for (; dlen; d++, dlen--) {
492 if (linelen > info->linemax)
493 info->linemax = linelen;
508 if (linelen > info->linemax)
509 info->linemax = linelen;
514 else if (ch == '\r') {
522 else if (ch == '\t' || ch == '\f') {
526 else if (ch < 32 || ch == 127)
530 if ((ch == 'F') || (ch == 'f'))
540 if (linelen == 2 && ch != 'r')
542 else if (linelen == 3 && ch != 'o')
544 else if (linelen == 4) {
557 if (ch != ' ' && ch != '\t')
562 s->whitespace = whitespace;
564 s->linelen = linelen;
569 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
570 #define BUGGY_ICONV 1
573 * Find the best charset conversion of the file from fromcode into one
574 * of the tocodes. If successful, set *tocode and CONTENT *info and
575 * return the number of characters converted inexactly. If no
576 * conversion was possible, return -1.
578 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
579 * which would otherwise prevent us from knowing the number of inexact
580 * conversions. Where the candidate target charset is UTF-8 we avoid
581 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
582 * fails with some libraries.
584 * We assume that the output from iconv is never more than 4 times as
585 * long as the input for any pair of charsets we might be interested
588 static size_t convert_file_to (FILE * file, const char *fromcode,
589 int ncodes, const char **tocodes,
590 int *tocode, CONTENT * info)
594 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
597 size_t ibl, obl, ubl, ubl1, n, ret;
600 CONTENT_STATE *states;
603 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
604 if (cd1 == (iconv_t) (-1))
607 cd = p_new(iconv_t, ncodes);
608 score = p_new(size_t, ncodes);
609 states = p_new(CONTENT_STATE, ncodes);
610 infos = p_new(CONTENT, ncodes);
612 for (i = 0; i < ncodes; i++)
613 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
614 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
616 /* Special case for conversion to UTF-8 */
617 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
623 /* Try to fill input buffer */
624 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
627 /* Convert to UTF-8 */
629 ob = bufu, obl = sizeof (bufu);
630 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
631 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
632 if (n == (size_t) (-1) &&
633 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
634 assert (errno == EILSEQ ||
635 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
641 /* Convert from UTF-8 */
642 for (i = 0; i < ncodes; i++)
643 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
644 ub = bufu, ubl = ubl1;
645 ob = bufo, obl = sizeof (bufo);
646 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
647 if (n == (size_t) (-1)) {
648 assert (errno == E2BIG ||
649 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
650 score[i] = (size_t) (-1);
654 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
657 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
658 /* Special case for conversion to UTF-8 */
659 update_content_info (&infos[i], &states[i], bufu, ubl1);
662 /* Save unused input */
663 memmove (bufi, ib, ibl);
664 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
671 /* Find best score */
673 for (i = 0; i < ncodes; i++) {
674 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
675 /* Special case for conversion to UTF-8 */
680 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
682 else if (ret == (size_t) (-1) || score[i] < ret) {
689 if (ret != (size_t) (-1)) {
690 memcpy (info, &infos[*tocode], sizeof (CONTENT));
691 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
695 for (i = 0; i < ncodes; i++)
696 if (cd[i] != (iconv_t) (-1))
708 #endif /* !HAVE_ICONV */
712 * Find the first of the fromcodes that gives a valid conversion and
713 * the best charset conversion of the file into one of the tocodes. If
714 * successful, set *fromcode and *tocode to dynamically allocated
715 * strings, set CONTENT *info, and return the number of characters
716 * converted inexactly. If no conversion was possible, return -1.
718 * Both fromcodes and tocodes may be colon-separated lists of charsets.
719 * However, if fromcode is zero then fromcodes is assumed to be the
720 * name of a single charset even if it contains a colon.
722 static size_t convert_file_from_to (FILE * file,
723 const char *fromcodes,
724 const char *tocodes, char **fromcode,
725 char **tocode, CONTENT * info)
733 /* Count the tocodes */
735 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
736 if ((c1 = strchr (c, ':')) == c)
742 tcode = p_new(char *, ncodes);
743 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
744 if ((c1 = strchr (c, ':')) == c)
746 tcode[i] = m_substrdup(c, c1);
751 /* Try each fromcode in turn */
752 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
753 if ((c1 = strchr (c, ':')) == c)
755 fcode = m_substrdup(c, c1);
757 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
759 if (ret != (size_t) (-1)) {
769 /* There is only one fromcode */
770 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
772 if (ret != (size_t) (-1)) {
779 for (i = 0; i < ncodes; i++)
788 * Analyze the contents of a file to determine which MIME encoding to use.
789 * Also set the body charset, sometimes, or not.
791 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
796 char *fromcode = NULL;
807 if (stat (fname, &sb) == -1) {
808 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
812 if (!S_ISREG (sb.st_mode)) {
813 mutt_error (_("%s isn't a regular file."), fname);
817 if ((fp = fopen (fname, "r")) == NULL) {
818 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
822 info = p_new(CONTENT, 1);
825 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
826 char *chs = mutt_get_parameter ("charset", b->parameter);
827 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
828 FileCharset : Charset) : Charset;
829 if (Charset && (chs || SendCharset) &&
830 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
831 &fromcode, &tocode, info) != (size_t) (-1)) {
833 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
834 mutt_set_parameter ("charset", chsbuf, &b->parameter);
836 b->file_charset = fromcode;
844 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
845 update_content_info (info, &state, buffer, r);
846 update_content_info (info, &state, 0, 0);
850 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
851 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
853 && !mutt_is_us_ascii (Charset) ? Charset :
854 "unknown-8bit"), &b->parameter);
859 /* Given a file with path ``s'', see if there is a registered MIME type.
860 * returns the major MIME type, and copies the subtype to ``d''. First look
861 * for ~/.mime.types, then look in a system mime.types if we can find one.
862 * The longest match is used so that we can match `ps.gz' when `gz' also
866 int mutt_lookup_mime_type (BODY * att, const char *path)
870 char buf[LONG_STRING];
871 char subtype[STRING], xtype[STRING];
873 int szf, sze, cur_sze;
881 szf = m_strlen(path);
883 for (count = 0; count < 4; count++) {
885 * can't use strtok() because we use it in an inner loop below, so use
886 * a switch statement here instead.
890 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
893 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
896 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
899 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
902 debug_print (1, ("Internal error, count = %d.\n", count));
903 goto bye; /* shouldn't happen */
906 if ((f = fopen (buf, "r")) != NULL) {
907 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
908 /* weed out any comments */
909 if ((p = strchr (buf, '#')))
912 /* remove any leading space. */
913 ct = vskipspaces(buf);
915 /* position on the next field in this line */
916 if ((p = strpbrk (ct, " \t")) == NULL)
921 /* cycle through the file extensions */
922 while ((p = strtok (p, " \t\n"))) {
924 if ((sze > cur_sze) && (szf >= sze) &&
925 (m_strcasecmp(path + szf - sze, p) == 0
926 || ascii_strcasecmp (path + szf - sze, p) == 0)
927 && (szf == sze || path[szf - sze - 1] == '.'))
929 /* get the content-type */
931 if ((p = strchr (ct, '/')) == NULL) {
932 /* malformed line, just skip it. */
937 for (q = p; *q && !ISSPACE (*q); q++);
939 m_strncpy(subtype, sizeof(subtype), p, q - p);
941 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
942 m_strcpy(xtype, sizeof(xtype), ct);
955 if (type != TYPEOTHER || *xtype != '\0') {
957 m_strreplace(&att->subtype, subtype);
958 m_strreplace(&att->xtype, xtype);
964 void mutt_message_to_7bit (BODY * a, FILE * fp)
966 char temp[_POSIX_PATH_MAX];
972 if (!a->filename && fp)
974 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
975 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
980 if (stat (a->filename, &sb) == -1) {
981 mutt_perror ("stat");
984 a->length = sb.st_size;
988 if (!(fpout = safe_fopen (temp, "w+"))) {
989 mutt_perror ("fopen");
993 fseeko (fpin, a->offset, 0);
994 a->parts = mutt_parse_messageRFC822 (fpin, a);
996 transform_to_7bit (a->parts, fpin);
998 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
999 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1001 fputs ("MIME-Version: 1.0\n", fpout);
1002 mutt_write_mime_header (a->parts, fpout);
1003 fputc ('\n', fpout);
1004 mutt_write_mime_body (a->parts, fpout);
1016 a->encoding = ENC7BIT;
1017 a->d_filename = a->filename;
1018 if (a->filename && a->unlink)
1019 unlink (a->filename);
1020 a->filename = m_strdup(temp);
1022 if (stat (a->filename, &sb) == -1) {
1023 mutt_perror ("stat");
1026 a->length = sb.st_size;
1027 mutt_free_body (&a->parts);
1028 a->hdr->content = NULL;
1031 static void transform_to_7bit (BODY * a, FILE * fpin)
1033 char buff[_POSIX_PATH_MAX];
1038 for (; a; a = a->next) {
1039 if (a->type == TYPEMULTIPART) {
1040 if (a->encoding != ENC7BIT)
1041 a->encoding = ENC7BIT;
1043 transform_to_7bit (a->parts, fpin);
1045 else if (mutt_is_message_type (a->type, a->subtype)) {
1046 mutt_message_to_7bit (a, fpin);
1050 a->force_charset = 1;
1053 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1054 mutt_perror ("fopen");
1058 mutt_decode_attachment (a, &s);
1060 a->d_filename = a->filename;
1061 a->filename = m_strdup(buff);
1063 if (stat (a->filename, &sb) == -1) {
1064 mutt_perror ("stat");
1067 a->length = sb.st_size;
1069 mutt_update_encoding (a);
1070 if (a->encoding == ENC8BIT)
1071 a->encoding = ENCQUOTEDPRINTABLE;
1072 else if (a->encoding == ENCBINARY)
1073 a->encoding = ENCBASE64;
1078 /* determine which Content-Transfer-Encoding to use */
1079 static void mutt_set_encoding (BODY * b, CONTENT * info)
1081 char send_charset[SHORT_STRING];
1083 if (b->type == TYPETEXT) {
1085 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1086 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1087 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1088 b->encoding = ENCQUOTEDPRINTABLE;
1089 else if (info->hibin)
1090 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1092 b->encoding = ENC7BIT;
1094 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1095 if (info->lobin || info->hibin) {
1096 if (option (OPTALLOW8BIT) && !info->lobin)
1097 b->encoding = ENC8BIT;
1099 mutt_message_to_7bit (b, NULL);
1102 b->encoding = ENC7BIT;
1104 else if (b->type == TYPEAPPLICATION
1105 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1106 b->encoding = ENC7BIT;
1109 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1110 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1113 /* Determine which encoding is smaller */
1114 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1115 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1116 b->encoding = ENCBASE64;
1118 b->encoding = ENCQUOTEDPRINTABLE;
1122 b->encoding = ENC7BIT;
1126 void mutt_stamp_attachment (BODY * a)
1128 a->stamp = time (NULL);
1131 /* Get a body's character set */
1133 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1137 if (b && b->type != TYPETEXT)
1141 p = mutt_get_parameter ("charset", b->parameter);
1144 mutt_canonical_charset (d, dlen, NONULL (p));
1146 m_strcpy(d, dlen, "us-ascii");
1152 /* Assumes called from send mode where BODY->filename points to actual file */
1153 void mutt_update_encoding (BODY * a)
1156 char chsbuff[STRING];
1158 /* override noconv when it's us-ascii */
1159 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1162 if (!a->force_charset && !a->noconv)
1163 mutt_delete_parameter ("charset", &a->parameter);
1165 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1168 mutt_set_encoding (a, info);
1169 mutt_stamp_attachment (a);
1171 p_delete(&a->content);
1176 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1178 char buffer[LONG_STRING];
1181 int cmflags, chflags;
1182 int pgp = hdr->security;
1184 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1185 (hdr->security & ENCRYPT)) {
1186 if (!crypt_valid_passphrase (hdr->security))
1190 mutt_mktemp (buffer);
1191 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1194 body = mutt_new_body ();
1195 body->type = TYPEMESSAGE;
1196 body->subtype = m_strdup("rfc822");
1197 body->filename = m_strdup(buffer);
1200 body->disposition = DISPINLINE;
1203 mutt_parse_mime_message (ctx, hdr);
1208 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1209 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1210 chflags |= CH_MIME | CH_TXTPLAIN;
1211 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1212 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1214 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1215 if (mutt_is_multipart_encrypted (hdr->content)) {
1216 chflags |= CH_MIME | CH_NONEWLINE;
1217 cmflags = M_CM_DECODE_PGP;
1220 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1221 chflags |= CH_MIME | CH_TXTPLAIN;
1222 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1225 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1226 chflags |= CH_MIME | CH_TXTPLAIN;
1227 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1228 pgp &= ~SMIMEENCRYPT;
1232 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1237 body->hdr = header_new();
1238 body->hdr->offset = 0;
1239 /* we don't need the user headers here */
1240 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1241 body->hdr->security = pgp;
1242 mutt_update_encoding (body);
1243 body->parts = body->hdr->content;
1250 BODY *mutt_make_file_attach (const char *path)
1255 att = mutt_new_body ();
1256 att->filename = m_strdup(path);
1258 /* Attempt to determine the appropriate content-type based on the filename
1265 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1266 path)) != TYPEOTHER || *xbuf != '\0') {
1268 att->subtype = m_strdup(buf);
1269 att->xtype = m_strdup(xbuf);
1274 mutt_lookup_mime_type (att, path);
1278 if ((info = mutt_get_content_info (path, att)) == NULL) {
1279 mutt_free_body (&att);
1283 if (!att->subtype) {
1284 if (info->lobin == 0
1285 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1287 * Statistically speaking, there should be more than 10% "lobin"
1288 * chars if this is really a binary file...
1290 att->type = TYPETEXT;
1291 att->subtype = m_strdup("plain");
1294 att->type = TYPEAPPLICATION;
1295 att->subtype = m_strdup("octet-stream");
1299 mutt_update_encoding (att);
1303 static int get_toplevel_encoding (BODY * a)
1307 for (; a; a = a->next) {
1308 if (a->encoding == ENCBINARY)
1310 else if (a->encoding == ENC8BIT)
1317 BODY *mutt_make_multipart (BODY * b)
1321 new = mutt_new_body ();
1322 new->type = TYPEMULTIPART;
1323 new->subtype = m_strdup("mixed");
1324 new->encoding = get_toplevel_encoding (b);
1325 mutt_generate_boundary (&new->parameter);
1327 new->disposition = DISPINLINE;
1333 /* remove the multipart body if it exists */
1334 BODY *mutt_remove_multipart (BODY * b)
1342 mutt_free_body (&t);
1347 char *mutt_make_date (char *s, size_t len)
1349 time_t t = time (NULL);
1350 struct tm *l = localtime (&t);
1351 time_t tz = mutt_local_tz (t);
1355 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1356 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1357 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1358 (int) tz / 60, (int) abs (tz) % 60);
1362 /* wrapper around mutt_write_address() so we can handle very large
1363 recipient lists without needing a huge temporary buffer in memory */
1364 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1368 char buf[LONG_STRING];
1376 rfc822_write_address (buf, sizeof (buf), adr, display);
1377 len = m_strlen(buf);
1378 if (count && linelen + len > 74) {
1380 linelen = len + 8; /* tab is usually about 8 spaces... */
1383 if (count && adr->mailbox) {
1391 if (!adr->group && adr->next && adr->next->mailbox) {
1401 /* arbitrary number of elements to grow the array by */
1406 /* need to write the list in reverse because they are stored in reverse order
1407 * when parsed to speed up threading
1409 void mutt_write_references (string_list_t * r, FILE * f)
1411 string_list_t **ref = NULL;
1412 int refcnt = 0, refmax = 0;
1414 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1415 if (refcnt == refmax)
1416 p_realloc(&ref, refmax += REF_INC);
1420 while (refcnt-- > 0) {
1422 fputs (ref[refcnt]->data, f);
1428 /* Note: all RFC2047 encoding should be done outside of this routine, except
1429 * for the "real name." This will allow this routine to be used more than
1430 * once, if necessary.
1432 * Likewise, all IDN processing should happen outside of this routine.
1434 * mode == 1 => "lite" mode (used for edit_hdrs)
1435 * mode == 0 => normal mode. write full header + MIME headers
1436 * mode == -1 => write just the envelope info (used for postponing messages)
1438 * privacy != 0 => will omit any headers which may identify the user.
1439 * Output generated is suitable for being sent through
1440 * anonymous remailer chains.
1444 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1445 int mode, int privacy)
1447 char buffer[LONG_STRING];
1449 string_list_t *tmp = env->userhdrs;
1450 int has_agent = 0; /* user defined user-agent header field exists */
1451 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1454 if (!option (OPTNEWSSEND))
1456 if (mode == 0 && !privacy)
1457 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1459 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1461 /* OPTUSEFROM is not consulted here so that we can still write a From:
1462 * field if the user sets it with the `my_hdr' command
1464 if (env->from && !privacy) {
1466 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1467 fprintf (fp, "From: %s\n", buffer);
1472 mutt_write_address_list (env->to, fp, 4, 0);
1476 if (!option (OPTNEWSSEND))
1478 if (EDIT_HEADER("To:"))
1479 fputs ("To:\n", fp);
1483 mutt_write_address_list (env->cc, fp, 4, 0);
1487 if (!option (OPTNEWSSEND))
1489 if (EDIT_HEADER("Cc:"))
1490 fputs ("Cc:\n", fp);
1493 if (mode != 0 || option (OPTWRITEBCC)) {
1494 fputs ("Bcc: ", fp);
1495 mutt_write_address_list (env->bcc, fp, 5, 0);
1500 if (!option (OPTNEWSSEND))
1502 if (EDIT_HEADER("Bcc:"))
1503 fputs ("Bcc:\n", fp);
1506 if (env->newsgroups)
1507 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1508 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1509 fputs ("Newsgroups:\n", fp);
1511 if (env->followup_to)
1512 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1513 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1514 fputs ("Followup-To:\n", fp);
1516 if (env->x_comment_to)
1517 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1518 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1519 EDIT_HEADER("X-Comment-To:"))
1520 fputs ("X-Comment-To:\n", fp);
1524 fprintf (fp, "Subject: %s\n", env->subject);
1525 else if (mode == 1 && EDIT_HEADER("Subject:"))
1526 fputs ("Subject:\n", fp);
1528 /* save message id if the user has set it */
1529 if (env->message_id && !privacy)
1530 fprintf (fp, "Message-ID: %s\n", env->message_id);
1532 if (env->reply_to) {
1533 fputs ("Reply-To: ", fp);
1534 mutt_write_address_list (env->reply_to, fp, 10, 0);
1536 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1537 fputs ("Reply-To:\n", fp);
1539 if (env->mail_followup_to)
1541 if (!option (OPTNEWSSEND))
1544 fputs ("Mail-Followup-To: ", fp);
1545 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1549 if (env->references) {
1550 fputs ("References:", fp);
1551 mutt_write_references (env->references, fp);
1555 /* Add the MIME headers */
1556 fputs ("MIME-Version: 1.0\n", fp);
1557 mutt_write_mime_header (attach, fp);
1560 if (env->in_reply_to) {
1561 fputs ("In-Reply-To:", fp);
1562 mutt_write_references (env->in_reply_to, fp);
1568 /* Add any user defined headers */
1569 for (; tmp; tmp = tmp->next) {
1570 if ((p = strchr (tmp->data, ':'))) {
1571 p = vskipspaces(p + 1);
1573 continue; /* don't emit empty fields. */
1575 /* check to see if the user has overridden the user-agent field */
1576 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1582 fputs (tmp->data, fp);
1587 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1590 if (OperatingSystem != NULL) {
1591 os = OperatingSystem;
1594 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1596 /* Add a vanity header */
1597 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1600 list_del (&hdrs, (list_del_t*)xmemfree);
1602 return (ferror (fp) == 0 ? 0 : -1);
1605 static void encode_headers (string_list_t * h)
1611 for (; h; h = h->next) {
1612 if (!(p = strchr (h->data, ':')))
1616 p = vskipspaces(p + 1);
1622 rfc2047_encode_string (&tmp);
1623 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1625 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1631 const char *mutt_fqdn (short may_hide_host)
1635 if (Fqdn && Fqdn[0] != '@') {
1638 if (may_hide_host && option (OPTHIDDENHOST)) {
1639 if ((p = strchr (Fqdn, '.')))
1642 /* sanity check: don't hide the host if
1643 * the fqdn is something like detebe.org.
1646 if (!p || !(q = strchr (p, '.')))
1654 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1655 static char mutt_normalized_char(char c)
1657 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1660 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1662 #define APPEND_FMT(fmt, arg) \
1664 int snlen = snprintf(buf, len, fmt, arg); \
1669 #define APPEND_BYTE(c) \
1686 APPEND_BYTE(mutt_normalized_char(c));
1694 APPEND_FMT("%02d", tm->tm_mday);
1697 APPEND_FMT("%02d", tm->tm_hour);
1700 APPEND_FMT("%02d", tm->tm_mon + 1);
1703 APPEND_FMT("%02d", tm->tm_min);
1706 APPEND_FMT("%lo", (unsigned long)now);
1709 APPEND_FMT("%u", (unsigned int)getpid());
1712 APPEND_FMT("%c", MsgIdPfx);
1713 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1716 APPEND_FMT("%u", (unsigned int)rand());
1719 APPEND_FMT("%x", (unsigned int)rand());
1722 APPEND_FMT("%02d", tm->tm_sec);
1725 APPEND_FMT("%u", (unsigned int) now);
1728 APPEND_FMT("%x", (unsigned int) now);
1730 case 'Y': /* this will break in the year 10000 ;-) */
1731 APPEND_FMT("%04d", tm->tm_year + 1900);
1736 default: /* invalid formats are replaced by '.' */
1738 m_strncat(buf, len, ".", 1);
1748 char *mutt_gen_msgid (void)
1750 char buf[SHORT_STRING];
1751 char localpart[SHORT_STRING];
1752 unsigned int localpart_length;
1755 if (!(fqdn = mutt_fqdn (0)))
1756 fqdn = NONULL (Hostname);
1758 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1760 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1762 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1763 return (m_strdup(buf));
1766 static RETSIGTYPE alarm_handler (int sig)
1771 /* invoke sendmail in a subshell
1772 path (in) path to program to execute
1773 args (in) arguments to pass to program
1774 msg (in) temp file containing message to send
1775 tempfile (out) if sendmail is put in the background, this points
1776 to the temporary file containing the stdout of the
1779 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1785 mutt_block_signals_system ();
1788 /* we also don't want to be stopped right now */
1789 sigaddset (&set, SIGTSTP);
1790 sigprocmask (SIG_BLOCK, &set, NULL);
1792 if (SendmailWait >= 0) {
1793 char tmp[_POSIX_PATH_MAX];
1796 *tempfile = m_strdup(tmp);
1799 if ((pid = fork ()) == 0) {
1800 struct sigaction act, oldalrm;
1802 /* save parent's ID before setsid() */
1805 /* we want the delivery to continue even after the main process dies,
1806 * so we put ourselves into another session right away
1810 /* next we close all open files */
1811 #if defined(OPEN_MAX)
1812 for (fd = 0; fd < OPEN_MAX; fd++)
1814 #elif defined(_POSIX_OPEN_MAX)
1815 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1823 /* now the second fork() */
1824 if ((pid = fork ()) == 0) {
1825 /* "msg" will be opened as stdin */
1826 if (open (msg, O_RDONLY, 0) < 0) {
1832 if (SendmailWait >= 0) {
1833 /* *tempfile will be opened as stdout */
1834 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1837 /* redirect stderr to *tempfile too */
1842 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1844 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1851 else if (pid == -1) {
1857 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1858 * SendmailWait = 0: wait forever
1859 * SendmailWait < 0: don't wait
1861 if (SendmailWait > 0) {
1863 act.sa_handler = alarm_handler;
1865 /* need to make sure waitpid() is interrupted on SIGALRM */
1866 act.sa_flags = SA_INTERRUPT;
1870 sigemptyset (&act.sa_mask);
1871 sigaction (SIGALRM, &act, &oldalrm);
1872 alarm (SendmailWait);
1874 else if (SendmailWait < 0)
1875 _exit (0xff & EX_OK);
1877 if (waitpid (pid, &st, 0) > 0) {
1878 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1879 if (SendmailWait && st == (0xff & EX_OK)) {
1880 unlink (*tempfile); /* no longer needed */
1885 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1886 if (SendmailWait > 0) {
1892 /* reset alarm; not really needed, but... */
1894 sigaction (SIGALRM, &oldalrm, NULL);
1896 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1897 /* the parent is already dead */
1905 sigprocmask (SIG_UNBLOCK, &set, NULL);
1907 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1908 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1910 st = S_ERR; /* error */
1912 mutt_unblock_signals_system (1);
1917 static const char **
1918 add_args(const char **args, size_t *argslen, size_t *argsmax, address_t * addr)
1920 for (; addr; addr = addr->next) {
1921 /* weed out group mailboxes, since those are for display only */
1922 if (addr->mailbox && !addr->group) {
1923 if (*argslen == *argsmax)
1924 p_realloc(&args, *argsmax += 5);
1925 args[(*argslen)++] = addr->mailbox;
1931 static const char **
1932 add_option(const char **args, size_t *argslen, size_t *argsmax, const char *s)
1934 if (*argslen == *argsmax) {
1935 p_realloc(&args, *argsmax += 5);
1937 args[(*argslen)++] = s;
1941 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1942 address_t * to, address_t * cc, address_t * bcc, /* recips */
1943 const char *msg, /* file containing message */
1945 { /* message contains 8bit chars */
1946 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1947 const char **args = NULL;
1948 size_t argslen = 0, argsmax = 0;
1952 if (option (OPTNEWSSEND)) {
1953 char cmd[LONG_STRING];
1955 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1958 i = nntp_post (msg);
1967 s = m_strdup(Sendmail);
1971 while ((ps = strtok (ps, " "))) {
1972 if (argslen == argsmax)
1973 p_realloc(&args, argsmax += 5);
1976 args[argslen++] = ps;
1978 path = m_strdup(ps);
1979 ps = strrchr (ps, '/');
1984 args[argslen++] = ps;
1991 if (!option (OPTNEWSSEND)) {
1993 if (eightbit && option (OPTUSE8BITMIME))
1994 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1996 if (option (OPTENVFROM)) {
1997 address_t *f = NULL;
2000 else if (from && !from->next)
2003 args = add_option (args, &argslen, &argsmax, "-f");
2004 args = add_args (args, &argslen, &argsmax, f);
2008 args = add_option (args, &argslen, &argsmax, "-N");
2009 args = add_option (args, &argslen, &argsmax, DsnNotify);
2012 args = add_option (args, &argslen, &argsmax, "-R");
2013 args = add_option (args, &argslen, &argsmax, DsnReturn);
2015 args = add_option (args, &argslen, &argsmax, "--");
2016 args = add_args (args, &argslen, &argsmax, to);
2017 args = add_args (args, &argslen, &argsmax, cc);
2018 args = add_args (args, &argslen, &argsmax, bcc);
2023 if (argslen == argsmax)
2024 p_realloc(&args, ++argsmax);
2026 args[argslen++] = NULL;
2028 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2030 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2035 if (stat (childout, &st) == 0 && st.st_size > 0)
2036 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2044 p_delete(&childout);
2049 if (i == (EX_OK & 0xff))
2051 else if (i == S_BKG)
2058 int mutt_invoke_mta (address_t * from, /* the sender */
2059 address_t * to, address_t * cc, address_t * bcc, /* recips */
2060 const char *msg, /* file containing message */
2062 { /* message contains 8bit chars */
2065 if (!option (OPTNEWSSEND))
2068 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2071 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2074 /* appends string 'b' to string 'a', and returns the pointer to the new
2076 char *mutt_append_string (char *a, const char *b)
2078 size_t la = m_strlen(a);
2080 p_realloc(&a, la + m_strlen(b) + 1);
2081 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2085 /* returns 1 if char `c' needs to be quoted to protect from shell
2086 interpretation when executing commands in a subshell */
2087 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2089 /* returns 1 if string `s' contains characters which could cause problems
2090 when used on a command line to execute a command */
2091 int mutt_needs_quote (const char *s)
2094 if (INVALID_CHAR (*s))
2101 /* Quote a string to prevent shell escapes when this string is used on the
2102 command line to send mail. */
2103 char *mutt_quote_string (const char *s)
2108 rlen = m_strlen(s) + 3;
2109 pr = r = p_new(char, rlen);
2112 if (INVALID_CHAR (*s)) {
2115 p_realloc(&r, ++rlen);
2126 /* For postponing (!final) do the necessary encodings only */
2127 void mutt_prepare_envelope (ENVELOPE * env, int final)
2129 char buffer[LONG_STRING];
2132 if (env->bcc && !(env->to || env->cc)) {
2133 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2134 * recipients if there is no To: or Cc: field, so attempt to suppress
2135 * it by using an empty To: field.
2137 env->to = address_new ();
2139 env->to->next = address_new ();
2142 rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
2145 env->to->mailbox = m_strdup(buffer);
2148 mutt_set_followup_to (env);
2150 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2151 env->message_id = mutt_gen_msgid ();
2154 /* Take care of 8-bit => 7-bit conversion. */
2155 rfc2047_encode_adrlist (env->to, "To");
2156 rfc2047_encode_adrlist (env->cc, "Cc");
2157 rfc2047_encode_adrlist (env->bcc, "Bcc");
2158 rfc2047_encode_adrlist (env->from, "From");
2159 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2160 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2164 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2167 rfc2047_encode_string (&env->subject);
2169 encode_headers (env->userhdrs);
2172 void mutt_unprepare_envelope (ENVELOPE * env)
2174 string_list_t *item;
2176 for (item = env->userhdrs; item; item = item->next)
2177 rfc2047_decode (&item->data);
2179 address_list_wipe(&env->mail_followup_to);
2181 /* back conversions */
2182 rfc2047_decode_adrlist (env->to);
2183 rfc2047_decode_adrlist (env->cc);
2184 rfc2047_decode_adrlist (env->bcc);
2185 rfc2047_decode_adrlist (env->from);
2186 rfc2047_decode_adrlist (env->reply_to);
2187 rfc2047_decode (&env->subject);
2190 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2191 const char *resent_from, address_t * env_from)
2195 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2196 MESSAGE *msg = NULL;
2199 /* Try to bounce each message out, aborting if we get any failures. */
2200 for (i = 0; i < Context->msgcount; i++)
2201 if (Context->hdrs[i]->tagged)
2203 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2208 /* If we failed to open a message, return with error */
2209 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2215 mutt_mktemp (tempfile);
2216 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2217 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2219 if (!option (OPTBOUNCEDELIVERED))
2220 ch_flags |= CH_WEED_DELIVERED;
2222 fseeko (fp, h->offset, 0);
2223 fprintf (f, "Resent-From: %s", resent_from);
2224 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2225 if (MsgIdFormat && *MsgIdFormat)
2226 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2227 fputs ("Resent-To: ", f);
2228 mutt_write_address_list (to, f, 11, 0);
2229 mutt_copy_header (fp, h, f, ch_flags, NULL);
2231 mutt_copy_bytes (fp, f, h->content->length);
2234 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2235 h->content->encoding == ENC8BIT);
2239 mx_close_message (&msg);
2244 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2247 const char *fqdn = mutt_fqdn (1);
2248 char resent_from[STRING];
2252 resent_from[0] = '\0';
2253 from = mutt_default_from ();
2256 rfc822_qualify (from, fqdn);
2258 rfc2047_encode_adrlist (from, "Resent-From");
2259 if (mutt_addrlist_to_idna (from, &err)) {
2260 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2263 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2266 unset_option (OPTNEWSSEND);
2269 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2271 address_list_wipe(&from);
2277 /* given a list of addresses, return a list of unique addresses */
2278 address_t *mutt_remove_duplicates (address_t * addr)
2280 address_t *top = addr;
2281 address_t **last = ⊤
2286 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2287 if (tmp->mailbox && addr->mailbox &&
2288 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2295 debug_print (2, ("Removing %s\n", addr->mailbox));
2300 address_list_wipe(&addr);
2313 static void set_noconv_flags (BODY * b, short flag)
2315 for (; b; b = b->next) {
2316 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2317 set_noconv_flags (b->parts, flag);
2318 else if (b->type == TYPETEXT && b->noconv) {
2320 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2322 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2327 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2328 int post, char *fcc)
2332 char tempfile[_POSIX_PATH_MAX];
2333 FILE *tempfp = NULL;
2337 set_noconv_flags (hdr->content, 1);
2339 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2340 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2344 /* We need to add a Content-Length field to avoid problems where a line in
2345 * the message body begins with "From "
2347 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2348 mutt_mktemp (tempfile);
2349 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2350 mutt_perror (tempfile);
2351 mx_close_mailbox (&f, NULL);
2356 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2357 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2358 mx_close_mailbox (&f, NULL);
2362 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2363 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2365 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2368 /* (postponment) if this was a reply of some sort, <msgid> contians the
2369 * Message-ID: of message replied to. Save it using a special X-Mutt-
2370 * header so it can be picked up if the message is recalled at a later
2371 * point in time. This will allow the message to be marked as replied if
2372 * the same mailbox is still open.
2375 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2377 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2378 * it can be picked up when the message is recalled
2381 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2382 fprintf (msg->fp, "Status: RO\n");
2386 /* (postponment) if the mail is to be signed or encrypted, save this info */
2387 if (post && (hdr->security & APPLICATION_PGP)) {
2388 fputs ("X-Mutt-PGP: ", msg->fp);
2389 if (hdr->security & ENCRYPT)
2390 fputc ('E', msg->fp);
2391 if (hdr->security & SIGN) {
2392 fputc ('S', msg->fp);
2393 if (PgpSignAs && *PgpSignAs)
2394 fprintf (msg->fp, "<%s>", PgpSignAs);
2396 if (hdr->security & INLINE)
2397 fputc ('I', msg->fp);
2398 fputc ('\n', msg->fp);
2401 /* (postponment) if the mail is to be signed or encrypted, save this info */
2402 if (post && (hdr->security & APPLICATION_SMIME)) {
2403 fputs ("X-Mutt-SMIME: ", msg->fp);
2404 if (hdr->security & ENCRYPT) {
2405 fputc ('E', msg->fp);
2406 if (SmimeCryptAlg && *SmimeCryptAlg)
2407 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2409 if (hdr->security & SIGN) {
2410 fputc ('S', msg->fp);
2411 if (SmimeDefaultKey && *SmimeDefaultKey)
2412 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2414 if (hdr->security & INLINE)
2415 fputc ('I', msg->fp);
2416 fputc ('\n', msg->fp);
2420 /* (postponement) if the mail is to be sent through a mixmaster
2421 * chain, save that information
2424 if (post && hdr->chain && hdr->chain) {
2427 fputs ("X-Mutt-Mix:", msg->fp);
2428 for (p = hdr->chain; p; p = p->next)
2429 fprintf (msg->fp, " %s", (char *) p->data);
2431 fputc ('\n', msg->fp);
2436 char sasha[LONG_STRING];
2439 mutt_write_mime_body (hdr->content, tempfp);
2441 /* make sure the last line ends with a newline. Emacs doesn't ensure
2442 * this will happen, and it can cause problems parsing the mailbox
2445 fseeko (tempfp, -1, 2);
2446 if (fgetc (tempfp) != '\n') {
2447 fseeko (tempfp, 0, 2);
2448 fputc ('\n', tempfp);
2452 if (ferror (tempfp)) {
2453 debug_print (1, ("%s: write failed.\n", tempfile));
2456 mx_commit_message (msg, &f); /* XXX - really? */
2457 mx_close_message (&msg);
2458 mx_close_mailbox (&f, NULL);
2462 /* count the number of lines */
2464 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2466 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2467 fprintf (msg->fp, "Lines: %d\n\n", lines);
2469 /* copy the body and clean up */
2471 r = mutt_copy_stream (tempfp, msg->fp);
2472 if (fclose (tempfp) != 0)
2474 /* if there was an error, leave the temp version */
2479 fputc ('\n', msg->fp); /* finish off the header */
2480 r = mutt_write_mime_body (hdr->content, msg->fp);
2483 if (mx_commit_message (msg, &f) != 0)
2485 mx_close_message (&msg);
2486 mx_close_mailbox (&f, NULL);
2489 set_noconv_flags (hdr->content, 0);