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>
24 #include <lib-ui/curses.h>
28 #include "recvattach.h"
33 #include <lib-crypt/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 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
74 static char MsgIdPfx = 'A';
76 static void transform_to_7bit (BODY * a, FILE * fpin);
78 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
81 char line[77], savechar;
83 while ((c = fgetconv (fc)) != EOF) {
84 /* Wrap the line if needed. */
85 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
86 /* If the last character is "quoted", then be sure to move all three
87 * characters to the next line. Otherwise, just move the last
90 if (line[linelen - 3] == '=') {
91 line[linelen - 3] = 0;
96 line[1] = line[linelen - 2];
97 line[2] = line[linelen - 1];
101 savechar = line[linelen - 1];
102 line[linelen - 1] = '=';
111 /* Escape lines that begin with/only contain "the message separator". */
112 if (linelen == 4 && !m_strncmp("From", line, 4)) {
113 m_strcpy(line, sizeof(line), "=46rom");
116 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
117 m_strcpy(line, sizeof(line), "=66rom");
120 else if (linelen == 1 && line[0] == '.') {
121 m_strcpy(line, sizeof(line), "=2E");
126 if (c == '\n' && istext) {
127 /* Check to make sure there is no trailing space on this line. */
129 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
131 sprintf (line + linelen - 1, "=%2.2X",
132 (unsigned char) line[linelen - 1]);
136 int savechar = line[linelen - 1];
138 line[linelen - 1] = '=';
141 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
151 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
152 /* Check to make sure there is enough room for the quoted character.
153 * If not, wrap to the next line.
156 line[linelen++] = '=';
162 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
166 /* Don't worry about wrapping the line here. That will happen during
167 * the next iteration when I'll also know what the next character is.
173 /* Take care of anything left in the buffer */
175 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
176 /* take care of trailing whitespace */
178 sprintf (line + linelen - 1, "=%2.2X",
179 (unsigned char) line[linelen - 1]);
181 savechar = line[linelen - 1];
182 line[linelen - 1] = '=';
186 sprintf (line, "=%2.2X", (unsigned char) savechar);
195 static char b64_buffer[3];
196 static short b64_num;
197 static short b64_linelen;
199 static void b64_flush (FILE * fout)
206 if (b64_linelen >= 72) {
211 for (i = b64_num; i < 3; i++)
212 b64_buffer[i] = '\0';
214 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
217 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
222 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
226 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
231 while (b64_linelen % 4) {
240 static void b64_putc (char c, FILE * fout)
245 b64_buffer[b64_num++] = c;
249 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
253 b64_num = b64_linelen = 0;
255 while ((ch = fgetconv (fc)) != EOF) {
256 if (istext && ch == '\n' && ch1 != '\r')
257 b64_putc ('\r', fout);
265 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
269 while ((ch = fgetconv (fc)) != EOF)
274 int mutt_write_mime_header (BODY * a, FILE * f)
284 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
287 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
289 for (p = a->parameter; p; p = p->next) {
298 tmp = m_strdup(p->value);
299 encode = rfc2231_encode_string (&tmp);
300 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
302 /* Dirty hack to make messages readable by Outlook Express
303 * for the Mac: force quotes around the boundary parameter
304 * even when they aren't needed.
307 if (!ascii_strcasecmp (p->attribute, "boundary")
308 && !strcmp (buffer, tmp))
309 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
313 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
315 if (len + tmplen + 2 > 76) {
324 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
332 fprintf (f, "Content-Description: %s\n", a->description);
334 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
337 if (!(fn = a->d_filename))
343 /* Strip off the leading path... */
344 if ((t = strrchr (fn, '/')))
351 encode = rfc2231_encode_string (&tmp);
352 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
354 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
360 if (a->encoding != ENC7BIT)
361 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
363 /* Do NOT add the terminator here!!! */
364 return (ferror (f) ? -1 : 0);
367 # define write_as_text_part(a) (mutt_is_text_part(a) || mutt_is_application_pgp(a))
369 int mutt_write_mime_body (BODY * a, FILE * f)
371 char *p, boundary[SHORT_STRING];
372 char send_charset[SHORT_STRING];
377 if (a->type == TYPEMULTIPART) {
378 /* First, find the boundary to use */
379 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
380 debug_print (1, ("no boundary parameter found!\n"));
381 mutt_error _("No boundary parameter found! [report this error]");
385 m_strcpy(boundary, sizeof(boundary), p);
387 for (t = a->parts; t; t = t->next) {
388 fprintf (f, "\n--%s\n", boundary);
389 if (mutt_write_mime_header (t, f) == -1)
392 if (mutt_write_mime_body (t, f) == -1)
395 fprintf (f, "\n--%s--\n", boundary);
396 return (ferror (f) ? -1 : 0);
399 /* This is pretty gross, but it's the best solution for now... */
400 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
401 fputs ("Version: 1\n", f);
405 if ((fpin = fopen (a->filename, "r")) == NULL) {
406 debug_print (1, ("%s no longer exists!\n", a->filename));
407 mutt_error (_("%s no longer exists!"), a->filename);
411 if (a->type == TYPETEXT && (!a->noconv))
412 fc = fgetconv_open (fpin, a->file_charset,
413 mutt_get_body_charset (send_charset,
414 sizeof (send_charset), a), 0);
416 fc = fgetconv_open (fpin, 0, 0, 0);
418 if (a->encoding == ENCQUOTEDPRINTABLE)
419 encode_quoted (fc, f, write_as_text_part (a));
420 else if (a->encoding == ENCBASE64)
421 encode_base64 (fc, f, write_as_text_part (a));
422 else if (a->type == TYPETEXT && (!a->noconv))
423 encode_8bit (fc, f, write_as_text_part (a));
425 mutt_copy_stream (fpin, f);
427 fgetconv_close (&fc);
430 return (ferror (f) ? -1 : 0);
433 #undef write_as_text_part
435 #define BOUNDARYLEN 16
436 void mutt_generate_boundary (PARAMETER ** parm)
438 char rs[BOUNDARYLEN + 1];
443 for (i = 0; i < BOUNDARYLEN; i++)
444 *p++ = __m_b64chars[LRAND() % sizeof(__m_b64chars)];
447 mutt_set_parameter ("boundary", rs, parm);
459 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
463 int whitespace = s->whitespace;
465 int linelen = s->linelen;
466 int was_cr = s->was_cr;
468 if (!d) { /* This signals EOF */
471 if (linelen > info->linemax)
472 info->linemax = linelen;
477 for (; dlen; d++, dlen--) {
490 if (linelen > info->linemax)
491 info->linemax = linelen;
506 if (linelen > info->linemax)
507 info->linemax = linelen;
512 else if (ch == '\r') {
520 else if (ch == '\t' || ch == '\f') {
524 else if (ch < 32 || ch == 127)
528 if ((ch == 'F') || (ch == 'f'))
538 if (linelen == 2 && ch != 'r')
540 else if (linelen == 3 && ch != 'o')
542 else if (linelen == 4) {
555 if (ch != ' ' && ch != '\t')
560 s->whitespace = whitespace;
562 s->linelen = linelen;
567 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
568 #define BUGGY_ICONV 1
571 * Find the best charset conversion of the file from fromcode into one
572 * of the tocodes. If successful, set *tocode and CONTENT *info and
573 * return the number of characters converted inexactly. If no
574 * conversion was possible, return -1.
576 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
577 * which would otherwise prevent us from knowing the number of inexact
578 * conversions. Where the candidate target charset is UTF-8 we avoid
579 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
580 * fails with some libraries.
582 * We assume that the output from iconv is never more than 4 times as
583 * long as the input for any pair of charsets we might be interested
586 static size_t convert_file_to (FILE * file, const char *fromcode,
587 int ncodes, const char **tocodes,
588 int *tocode, CONTENT * info)
592 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
595 size_t ibl, obl, ubl, ubl1, n, ret;
598 CONTENT_STATE *states;
601 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
602 if (cd1 == (iconv_t) (-1))
605 cd = p_new(iconv_t, ncodes);
606 score = p_new(size_t, ncodes);
607 states = p_new(CONTENT_STATE, ncodes);
608 infos = p_new(CONTENT, ncodes);
610 for (i = 0; i < ncodes; i++)
611 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
612 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
614 /* Special case for conversion to UTF-8 */
615 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
621 /* Try to fill input buffer */
622 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
625 /* Convert to UTF-8 */
627 ob = bufu, obl = sizeof (bufu);
628 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
629 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
630 if (n == (size_t) (-1) &&
631 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
632 assert (errno == EILSEQ ||
633 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
639 /* Convert from UTF-8 */
640 for (i = 0; i < ncodes; i++)
641 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
642 ub = bufu, ubl = ubl1;
643 ob = bufo, obl = sizeof (bufo);
644 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
645 if (n == (size_t) (-1)) {
646 assert (errno == E2BIG ||
647 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
648 score[i] = (size_t) (-1);
652 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
655 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
656 /* Special case for conversion to UTF-8 */
657 update_content_info (&infos[i], &states[i], bufu, ubl1);
660 /* Save unused input */
661 memmove (bufi, ib, ibl);
662 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
669 /* Find best score */
671 for (i = 0; i < ncodes; i++) {
672 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
673 /* Special case for conversion to UTF-8 */
678 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
680 else if (ret == (size_t) (-1) || score[i] < ret) {
687 if (ret != (size_t) (-1)) {
688 memcpy (info, &infos[*tocode], sizeof (CONTENT));
689 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
693 for (i = 0; i < ncodes; i++)
694 if (cd[i] != (iconv_t) (-1))
706 #endif /* !HAVE_ICONV */
710 * Find the first of the fromcodes that gives a valid conversion and
711 * the best charset conversion of the file into one of the tocodes. If
712 * successful, set *fromcode and *tocode to dynamically allocated
713 * strings, set CONTENT *info, and return the number of characters
714 * converted inexactly. If no conversion was possible, return -1.
716 * Both fromcodes and tocodes may be colon-separated lists of charsets.
717 * However, if fromcode is zero then fromcodes is assumed to be the
718 * name of a single charset even if it contains a colon.
720 static size_t convert_file_from_to (FILE * file,
721 const char *fromcodes,
722 const char *tocodes, char **fromcode,
723 char **tocode, CONTENT * info)
731 /* Count the tocodes */
733 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
734 if ((c1 = strchr (c, ':')) == c)
740 tcode = p_new(char *, ncodes);
741 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
742 if ((c1 = strchr (c, ':')) == c)
744 tcode[i] = m_substrdup(c, c1);
749 /* Try each fromcode in turn */
750 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
751 if ((c1 = strchr (c, ':')) == c)
753 fcode = m_substrdup(c, c1);
755 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
757 if (ret != (size_t) (-1)) {
767 /* There is only one fromcode */
768 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
770 if (ret != (size_t) (-1)) {
777 for (i = 0; i < ncodes; i++)
786 * Analyze the contents of a file to determine which MIME encoding to use.
787 * Also set the body charset, sometimes, or not.
789 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
794 char *fromcode = NULL;
805 if (stat (fname, &sb) == -1) {
806 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
810 if (!S_ISREG (sb.st_mode)) {
811 mutt_error (_("%s isn't a regular file."), fname);
815 if ((fp = fopen (fname, "r")) == NULL) {
816 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
820 info = p_new(CONTENT, 1);
823 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
824 char *chs = mutt_get_parameter ("charset", b->parameter);
825 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
826 FileCharset : Charset) : Charset;
827 if (Charset && (chs || SendCharset) &&
828 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
829 &fromcode, &tocode, info) != (size_t) (-1)) {
831 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
832 mutt_set_parameter ("charset", chsbuf, &b->parameter);
834 b->file_charset = fromcode;
842 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
843 update_content_info (info, &state, buffer, r);
844 update_content_info (info, &state, 0, 0);
848 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
849 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
851 && !mutt_is_us_ascii (Charset) ? Charset :
852 "unknown-8bit"), &b->parameter);
857 /* Given a file with path ``s'', see if there is a registered MIME type.
858 * returns the major MIME type, and copies the subtype to ``d''. First look
859 * for ~/.mime.types, then look in a system mime.types if we can find one.
860 * The longest match is used so that we can match `ps.gz' when `gz' also
864 int mutt_lookup_mime_type (BODY * att, const char *path)
868 char buf[LONG_STRING];
869 char subtype[STRING], xtype[STRING];
871 int szf, sze, cur_sze;
879 szf = m_strlen(path);
881 for (count = 0; count < 4; count++) {
883 * can't use strtok() because we use it in an inner loop below, so use
884 * a switch statement here instead.
888 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
891 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
894 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
897 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
900 debug_print (1, ("Internal error, count = %d.\n", count));
901 goto bye; /* shouldn't happen */
904 if ((f = fopen (buf, "r")) != NULL) {
905 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
906 /* weed out any comments */
907 if ((p = strchr (buf, '#')))
910 /* remove any leading space. */
911 ct = vskipspaces(buf);
913 /* position on the next field in this line */
914 if ((p = strpbrk (ct, " \t")) == NULL)
919 /* cycle through the file extensions */
920 while ((p = strtok (p, " \t\n"))) {
922 if ((sze > cur_sze) && (szf >= sze) &&
923 (m_strcasecmp(path + szf - sze, p) == 0
924 || ascii_strcasecmp (path + szf - sze, p) == 0)
925 && (szf == sze || path[szf - sze - 1] == '.'))
927 /* get the content-type */
929 if ((p = strchr (ct, '/')) == NULL) {
930 /* malformed line, just skip it. */
935 for (q = p; *q && !ISSPACE (*q); q++);
937 m_strncpy(subtype, sizeof(subtype), p, q - p);
939 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
940 m_strcpy(xtype, sizeof(xtype), ct);
953 if (type != TYPEOTHER || *xtype != '\0') {
955 m_strreplace(&att->subtype, subtype);
956 m_strreplace(&att->xtype, xtype);
962 void mutt_message_to_7bit (BODY * a, FILE * fp)
964 char temp[_POSIX_PATH_MAX];
970 if (!a->filename && fp)
972 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
973 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
978 if (stat (a->filename, &sb) == -1) {
979 mutt_perror ("stat");
982 a->length = sb.st_size;
986 if (!(fpout = safe_fopen (temp, "w+"))) {
987 mutt_perror ("fopen");
991 fseeko (fpin, a->offset, 0);
992 a->parts = mutt_parse_messageRFC822 (fpin, a);
994 transform_to_7bit (a->parts, fpin);
996 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
997 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
999 fputs ("MIME-Version: 1.0\n", fpout);
1000 mutt_write_mime_header (a->parts, fpout);
1001 fputc ('\n', fpout);
1002 mutt_write_mime_body (a->parts, fpout);
1014 a->encoding = ENC7BIT;
1015 a->d_filename = a->filename;
1016 if (a->filename && a->unlink)
1017 unlink (a->filename);
1018 a->filename = m_strdup(temp);
1020 if (stat (a->filename, &sb) == -1) {
1021 mutt_perror ("stat");
1024 a->length = sb.st_size;
1025 mutt_free_body (&a->parts);
1026 a->hdr->content = NULL;
1029 static void transform_to_7bit (BODY * a, FILE * fpin)
1031 char buff[_POSIX_PATH_MAX];
1036 for (; a; a = a->next) {
1037 if (a->type == TYPEMULTIPART) {
1038 if (a->encoding != ENC7BIT)
1039 a->encoding = ENC7BIT;
1041 transform_to_7bit (a->parts, fpin);
1043 else if (mutt_is_message_type (a->type, a->subtype)) {
1044 mutt_message_to_7bit (a, fpin);
1048 a->force_charset = 1;
1051 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1052 mutt_perror ("fopen");
1056 mutt_decode_attachment (a, &s);
1058 a->d_filename = a->filename;
1059 a->filename = m_strdup(buff);
1061 if (stat (a->filename, &sb) == -1) {
1062 mutt_perror ("stat");
1065 a->length = sb.st_size;
1067 mutt_update_encoding (a);
1068 if (a->encoding == ENC8BIT)
1069 a->encoding = ENCQUOTEDPRINTABLE;
1070 else if (a->encoding == ENCBINARY)
1071 a->encoding = ENCBASE64;
1076 /* determine which Content-Transfer-Encoding to use */
1077 static void mutt_set_encoding (BODY * b, CONTENT * info)
1079 char send_charset[SHORT_STRING];
1081 if (b->type == TYPETEXT) {
1083 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1084 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1085 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1086 b->encoding = ENCQUOTEDPRINTABLE;
1087 else if (info->hibin)
1088 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1090 b->encoding = ENC7BIT;
1092 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1093 if (info->lobin || info->hibin) {
1094 if (option (OPTALLOW8BIT) && !info->lobin)
1095 b->encoding = ENC8BIT;
1097 mutt_message_to_7bit (b, NULL);
1100 b->encoding = ENC7BIT;
1102 else if (b->type == TYPEAPPLICATION
1103 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1104 b->encoding = ENC7BIT;
1107 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1108 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1111 /* Determine which encoding is smaller */
1112 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1113 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1114 b->encoding = ENCBASE64;
1116 b->encoding = ENCQUOTEDPRINTABLE;
1120 b->encoding = ENC7BIT;
1124 void mutt_stamp_attachment (BODY * a)
1126 a->stamp = time (NULL);
1129 /* Get a body's character set */
1131 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1135 if (b && b->type != TYPETEXT)
1139 p = mutt_get_parameter ("charset", b->parameter);
1142 mutt_canonical_charset (d, dlen, NONULL (p));
1144 m_strcpy(d, dlen, "us-ascii");
1150 /* Assumes called from send mode where BODY->filename points to actual file */
1151 void mutt_update_encoding (BODY * a)
1154 char chsbuff[STRING];
1156 /* override noconv when it's us-ascii */
1157 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1160 if (!a->force_charset && !a->noconv)
1161 mutt_delete_parameter ("charset", &a->parameter);
1163 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1166 mutt_set_encoding (a, info);
1167 mutt_stamp_attachment (a);
1169 p_delete(&a->content);
1174 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1176 char buffer[LONG_STRING];
1179 int cmflags, chflags;
1180 int pgp = hdr->security;
1182 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1183 (hdr->security & ENCRYPT)) {
1184 if (!crypt_valid_passphrase (hdr->security))
1188 mutt_mktemp (buffer);
1189 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1192 body = mutt_new_body ();
1193 body->type = TYPEMESSAGE;
1194 body->subtype = m_strdup("rfc822");
1195 body->filename = m_strdup(buffer);
1198 body->disposition = DISPINLINE;
1201 mutt_parse_mime_message (ctx, hdr);
1206 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1207 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1208 chflags |= CH_MIME | CH_TXTPLAIN;
1209 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1210 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1212 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1213 if (mutt_is_multipart_encrypted (hdr->content)) {
1214 chflags |= CH_MIME | CH_NONEWLINE;
1215 cmflags = M_CM_DECODE_PGP;
1218 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1219 chflags |= CH_MIME | CH_TXTPLAIN;
1220 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1223 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1224 chflags |= CH_MIME | CH_TXTPLAIN;
1225 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1226 pgp &= ~SMIMEENCRYPT;
1230 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1235 body->hdr = header_new();
1236 body->hdr->offset = 0;
1237 /* we don't need the user headers here */
1238 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1239 body->hdr->security = pgp;
1240 mutt_update_encoding (body);
1241 body->parts = body->hdr->content;
1248 BODY *mutt_make_file_attach (const char *path)
1253 att = mutt_new_body ();
1254 att->filename = m_strdup(path);
1256 /* Attempt to determine the appropriate content-type based on the filename
1263 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1264 path)) != TYPEOTHER || *xbuf != '\0') {
1266 att->subtype = m_strdup(buf);
1267 att->xtype = m_strdup(xbuf);
1272 mutt_lookup_mime_type (att, path);
1276 if ((info = mutt_get_content_info (path, att)) == NULL) {
1277 mutt_free_body (&att);
1281 if (!att->subtype) {
1282 if (info->lobin == 0
1283 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1285 * Statistically speaking, there should be more than 10% "lobin"
1286 * chars if this is really a binary file...
1288 att->type = TYPETEXT;
1289 att->subtype = m_strdup("plain");
1292 att->type = TYPEAPPLICATION;
1293 att->subtype = m_strdup("octet-stream");
1297 mutt_update_encoding (att);
1301 static int get_toplevel_encoding (BODY * a)
1305 for (; a; a = a->next) {
1306 if (a->encoding == ENCBINARY)
1308 else if (a->encoding == ENC8BIT)
1315 BODY *mutt_make_multipart (BODY * b)
1319 new = mutt_new_body ();
1320 new->type = TYPEMULTIPART;
1321 new->subtype = m_strdup("mixed");
1322 new->encoding = get_toplevel_encoding (b);
1323 mutt_generate_boundary (&new->parameter);
1325 new->disposition = DISPINLINE;
1331 /* remove the multipart body if it exists */
1332 BODY *mutt_remove_multipart (BODY * b)
1340 mutt_free_body (&t);
1345 char *mutt_make_date (char *s, size_t len)
1347 time_t t = time (NULL);
1348 struct tm *l = localtime (&t);
1349 time_t tz = mutt_local_tz (t);
1353 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1354 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1355 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1356 (int) tz / 60, (int) abs (tz) % 60);
1360 /* wrapper around mutt_write_address() so we can handle very large
1361 recipient lists without needing a huge temporary buffer in memory */
1362 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1366 char buf[LONG_STRING];
1374 rfc822_write_address (buf, sizeof (buf), adr, display);
1375 len = m_strlen(buf);
1376 if (count && linelen + len > 74) {
1378 linelen = len + 8; /* tab is usually about 8 spaces... */
1381 if (count && adr->mailbox) {
1389 if (!adr->group && adr->next && adr->next->mailbox) {
1399 /* arbitrary number of elements to grow the array by */
1404 /* need to write the list in reverse because they are stored in reverse order
1405 * when parsed to speed up threading
1407 void mutt_write_references (LIST * r, FILE * f)
1410 int refcnt = 0, refmax = 0;
1412 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1413 if (refcnt == refmax)
1414 p_realloc(&ref, refmax += REF_INC);
1418 while (refcnt-- > 0) {
1420 fputs (ref[refcnt]->data, f);
1426 /* Note: all RFC2047 encoding should be done outside of this routine, except
1427 * for the "real name." This will allow this routine to be used more than
1428 * once, if necessary.
1430 * Likewise, all IDN processing should happen outside of this routine.
1432 * mode == 1 => "lite" mode (used for edit_hdrs)
1433 * mode == 0 => normal mode. write full header + MIME headers
1434 * mode == -1 => write just the envelope info (used for postponing messages)
1436 * privacy != 0 => will omit any headers which may identify the user.
1437 * Output generated is suitable for being sent through
1438 * anonymous remailer chains.
1442 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1443 int mode, int privacy)
1445 char buffer[LONG_STRING];
1447 LIST *tmp = env->userhdrs;
1448 int has_agent = 0; /* user defined user-agent header field exists */
1449 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1452 if (!option (OPTNEWSSEND))
1454 if (mode == 0 && !privacy)
1455 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1457 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1459 /* OPTUSEFROM is not consulted here so that we can still write a From:
1460 * field if the user sets it with the `my_hdr' command
1462 if (env->from && !privacy) {
1464 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1465 fprintf (fp, "From: %s\n", buffer);
1470 mutt_write_address_list (env->to, fp, 4, 0);
1474 if (!option (OPTNEWSSEND))
1476 if (EDIT_HEADER("To:"))
1477 fputs ("To:\n", fp);
1481 mutt_write_address_list (env->cc, fp, 4, 0);
1485 if (!option (OPTNEWSSEND))
1487 if (EDIT_HEADER("Cc:"))
1488 fputs ("Cc:\n", fp);
1491 if (mode != 0 || option (OPTWRITEBCC)) {
1492 fputs ("Bcc: ", fp);
1493 mutt_write_address_list (env->bcc, fp, 5, 0);
1498 if (!option (OPTNEWSSEND))
1500 if (EDIT_HEADER("Bcc:"))
1501 fputs ("Bcc:\n", fp);
1504 if (env->newsgroups)
1505 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1506 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1507 fputs ("Newsgroups:\n", fp);
1509 if (env->followup_to)
1510 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1511 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1512 fputs ("Followup-To:\n", fp);
1514 if (env->x_comment_to)
1515 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1516 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1517 EDIT_HEADER("X-Comment-To:"))
1518 fputs ("X-Comment-To:\n", fp);
1522 fprintf (fp, "Subject: %s\n", env->subject);
1523 else if (mode == 1 && EDIT_HEADER("Subject:"))
1524 fputs ("Subject:\n", fp);
1526 /* save message id if the user has set it */
1527 if (env->message_id && !privacy)
1528 fprintf (fp, "Message-ID: %s\n", env->message_id);
1530 if (env->reply_to) {
1531 fputs ("Reply-To: ", fp);
1532 mutt_write_address_list (env->reply_to, fp, 10, 0);
1534 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1535 fputs ("Reply-To:\n", fp);
1537 if (env->mail_followup_to)
1539 if (!option (OPTNEWSSEND))
1542 fputs ("Mail-Followup-To: ", fp);
1543 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1547 if (env->references) {
1548 fputs ("References:", fp);
1549 mutt_write_references (env->references, fp);
1553 /* Add the MIME headers */
1554 fputs ("MIME-Version: 1.0\n", fp);
1555 mutt_write_mime_header (attach, fp);
1558 if (env->in_reply_to) {
1559 fputs ("In-Reply-To:", fp);
1560 mutt_write_references (env->in_reply_to, fp);
1566 /* Add any user defined headers */
1567 for (; tmp; tmp = tmp->next) {
1568 if ((p = strchr (tmp->data, ':'))) {
1569 p = vskipspaces(p + 1);
1571 continue; /* don't emit empty fields. */
1573 /* check to see if the user has overridden the user-agent field */
1574 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1580 fputs (tmp->data, fp);
1585 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1588 if (OperatingSystem != NULL) {
1589 os = OperatingSystem;
1592 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1594 /* Add a vanity header */
1595 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1598 list_del (&hdrs, (list_del_t*)xmemfree);
1600 return (ferror (fp) == 0 ? 0 : -1);
1603 static void encode_headers (LIST * h)
1609 for (; h; h = h->next) {
1610 if (!(p = strchr (h->data, ':')))
1614 p = vskipspaces(p + 1);
1620 rfc2047_encode_string (&tmp);
1621 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1623 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1629 const char *mutt_fqdn (short may_hide_host)
1633 if (Fqdn && Fqdn[0] != '@') {
1636 if (may_hide_host && option (OPTHIDDENHOST)) {
1637 if ((p = strchr (Fqdn, '.')))
1640 /* sanity check: don't hide the host if
1641 * the fqdn is something like detebe.org.
1644 if (!p || !(q = strchr (p, '.')))
1652 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1653 static char mutt_normalized_char(char c)
1655 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1658 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1660 #define APPEND_FMT(fmt, arg) \
1662 int snlen = snprintf(buf, len, fmt, arg); \
1667 #define APPEND_BYTE(c) \
1684 APPEND_BYTE(mutt_normalized_char(c));
1692 APPEND_FMT("%02d", tm->tm_mday);
1695 APPEND_FMT("%02d", tm->tm_hour);
1698 APPEND_FMT("%02d", tm->tm_mon + 1);
1701 APPEND_FMT("%02d", tm->tm_min);
1704 APPEND_FMT("%lo", (unsigned long)now);
1707 APPEND_FMT("%u", (unsigned int)getpid());
1710 APPEND_FMT("%c", MsgIdPfx);
1711 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1714 APPEND_FMT("%u", (unsigned int)rand());
1717 APPEND_FMT("%x", (unsigned int)rand());
1720 APPEND_FMT("%02d", tm->tm_sec);
1723 APPEND_FMT("%u", (unsigned int) now);
1726 APPEND_FMT("%x", (unsigned int) now);
1728 case 'Y': /* this will break in the year 10000 ;-) */
1729 APPEND_FMT("%04d", tm->tm_year + 1900);
1734 default: /* invalid formats are replaced by '.' */
1736 m_strncat(buf, len, ".", 1);
1746 char *mutt_gen_msgid (void)
1748 char buf[SHORT_STRING];
1749 char localpart[SHORT_STRING];
1750 unsigned int localpart_length;
1753 if (!(fqdn = mutt_fqdn (0)))
1754 fqdn = NONULL (Hostname);
1756 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1758 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1760 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1761 return (m_strdup(buf));
1764 static RETSIGTYPE alarm_handler (int sig)
1769 /* invoke sendmail in a subshell
1770 path (in) path to program to execute
1771 args (in) arguments to pass to program
1772 msg (in) temp file containing message to send
1773 tempfile (out) if sendmail is put in the background, this points
1774 to the temporary file containing the stdout of the
1777 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1783 mutt_block_signals_system ();
1786 /* we also don't want to be stopped right now */
1787 sigaddset (&set, SIGTSTP);
1788 sigprocmask (SIG_BLOCK, &set, NULL);
1790 if (SendmailWait >= 0) {
1791 char tmp[_POSIX_PATH_MAX];
1794 *tempfile = m_strdup(tmp);
1797 if ((pid = fork ()) == 0) {
1798 struct sigaction act, oldalrm;
1800 /* save parent's ID before setsid() */
1803 /* we want the delivery to continue even after the main process dies,
1804 * so we put ourselves into another session right away
1808 /* next we close all open files */
1809 #if defined(OPEN_MAX)
1810 for (fd = 0; fd < OPEN_MAX; fd++)
1812 #elif defined(_POSIX_OPEN_MAX)
1813 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1821 /* now the second fork() */
1822 if ((pid = fork ()) == 0) {
1823 /* "msg" will be opened as stdin */
1824 if (open (msg, O_RDONLY, 0) < 0) {
1830 if (SendmailWait >= 0) {
1831 /* *tempfile will be opened as stdout */
1832 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1835 /* redirect stderr to *tempfile too */
1840 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1842 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1849 else if (pid == -1) {
1855 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1856 * SendmailWait = 0: wait forever
1857 * SendmailWait < 0: don't wait
1859 if (SendmailWait > 0) {
1861 act.sa_handler = alarm_handler;
1863 /* need to make sure waitpid() is interrupted on SIGALRM */
1864 act.sa_flags = SA_INTERRUPT;
1868 sigemptyset (&act.sa_mask);
1869 sigaction (SIGALRM, &act, &oldalrm);
1870 alarm (SendmailWait);
1872 else if (SendmailWait < 0)
1873 _exit (0xff & EX_OK);
1875 if (waitpid (pid, &st, 0) > 0) {
1876 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1877 if (SendmailWait && st == (0xff & EX_OK)) {
1878 unlink (*tempfile); /* no longer needed */
1883 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1884 if (SendmailWait > 0) {
1890 /* reset alarm; not really needed, but... */
1892 sigaction (SIGALRM, &oldalrm, NULL);
1894 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1895 /* the parent is already dead */
1903 sigprocmask (SIG_UNBLOCK, &set, NULL);
1905 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1906 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1908 st = S_ERR; /* error */
1910 mutt_unblock_signals_system (1);
1915 static const char **
1916 add_args(const char **args, size_t *argslen, size_t *argsmax, address_t * addr)
1918 for (; addr; addr = addr->next) {
1919 /* weed out group mailboxes, since those are for display only */
1920 if (addr->mailbox && !addr->group) {
1921 if (*argslen == *argsmax)
1922 p_realloc(&args, *argsmax += 5);
1923 args[(*argslen)++] = addr->mailbox;
1929 static const char **
1930 add_option(const char **args, size_t *argslen, size_t *argsmax, const char *s)
1932 if (*argslen == *argsmax) {
1933 p_realloc(&args, *argsmax += 5);
1935 args[(*argslen)++] = s;
1939 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1940 address_t * to, address_t * cc, address_t * bcc, /* recips */
1941 const char *msg, /* file containing message */
1943 { /* message contains 8bit chars */
1944 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1945 const char **args = NULL;
1946 size_t argslen = 0, argsmax = 0;
1950 if (option (OPTNEWSSEND)) {
1951 char cmd[LONG_STRING];
1953 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1956 i = nntp_post (msg);
1965 s = m_strdup(Sendmail);
1969 while ((ps = strtok (ps, " "))) {
1970 if (argslen == argsmax)
1971 p_realloc(&args, argsmax += 5);
1974 args[argslen++] = ps;
1976 path = m_strdup(ps);
1977 ps = strrchr (ps, '/');
1982 args[argslen++] = ps;
1989 if (!option (OPTNEWSSEND)) {
1991 if (eightbit && option (OPTUSE8BITMIME))
1992 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1994 if (option (OPTENVFROM)) {
1995 address_t *f = NULL;
1998 else if (from && !from->next)
2001 args = add_option (args, &argslen, &argsmax, "-f");
2002 args = add_args (args, &argslen, &argsmax, f);
2006 args = add_option (args, &argslen, &argsmax, "-N");
2007 args = add_option (args, &argslen, &argsmax, DsnNotify);
2010 args = add_option (args, &argslen, &argsmax, "-R");
2011 args = add_option (args, &argslen, &argsmax, DsnReturn);
2013 args = add_option (args, &argslen, &argsmax, "--");
2014 args = add_args (args, &argslen, &argsmax, to);
2015 args = add_args (args, &argslen, &argsmax, cc);
2016 args = add_args (args, &argslen, &argsmax, bcc);
2021 if (argslen == argsmax)
2022 p_realloc(&args, ++argsmax);
2024 args[argslen++] = NULL;
2026 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2028 const char *e = mutt_strsysexit (i);
2030 e = mutt_strsysexit (i);
2031 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2036 if (stat (childout, &st) == 0 && st.st_size > 0)
2037 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2045 p_delete(&childout);
2050 if (i == (EX_OK & 0xff))
2052 else if (i == S_BKG)
2059 int mutt_invoke_mta (address_t * from, /* the sender */
2060 address_t * to, address_t * cc, address_t * bcc, /* recips */
2061 const char *msg, /* file containing message */
2063 { /* message contains 8bit chars */
2066 if (!option (OPTNEWSSEND))
2069 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2072 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2075 /* appends string 'b' to string 'a', and returns the pointer to the new
2077 char *mutt_append_string (char *a, const char *b)
2079 size_t la = m_strlen(a);
2081 p_realloc(&a, la + m_strlen(b) + 1);
2082 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2086 /* returns 1 if char `c' needs to be quoted to protect from shell
2087 interpretation when executing commands in a subshell */
2088 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2090 /* returns 1 if string `s' contains characters which could cause problems
2091 when used on a command line to execute a command */
2092 int mutt_needs_quote (const char *s)
2095 if (INVALID_CHAR (*s))
2102 /* Quote a string to prevent shell escapes when this string is used on the
2103 command line to send mail. */
2104 char *mutt_quote_string (const char *s)
2109 rlen = m_strlen(s) + 3;
2110 pr = r = p_new(char, rlen);
2113 if (INVALID_CHAR (*s)) {
2116 p_realloc(&r, ++rlen);
2127 /* For postponing (!final) do the necessary encodings only */
2128 void mutt_prepare_envelope (ENVELOPE * env, int final)
2130 char buffer[LONG_STRING];
2133 if (env->bcc && !(env->to || env->cc)) {
2134 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2135 * recipients if there is no To: or Cc: field, so attempt to suppress
2136 * it by using an empty To: field.
2138 env->to = address_new ();
2140 env->to->next = address_new ();
2143 rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
2146 env->to->mailbox = m_strdup(buffer);
2149 mutt_set_followup_to (env);
2151 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2152 env->message_id = mutt_gen_msgid ();
2155 /* Take care of 8-bit => 7-bit conversion. */
2156 rfc2047_encode_adrlist (env->to, "To");
2157 rfc2047_encode_adrlist (env->cc, "Cc");
2158 rfc2047_encode_adrlist (env->bcc, "Bcc");
2159 rfc2047_encode_adrlist (env->from, "From");
2160 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2161 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2165 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2168 rfc2047_encode_string (&env->subject);
2170 encode_headers (env->userhdrs);
2173 void mutt_unprepare_envelope (ENVELOPE * env)
2177 for (item = env->userhdrs; item; item = item->next)
2178 rfc2047_decode (&item->data);
2180 address_delete (&env->mail_followup_to);
2182 /* back conversions */
2183 rfc2047_decode_adrlist (env->to);
2184 rfc2047_decode_adrlist (env->cc);
2185 rfc2047_decode_adrlist (env->bcc);
2186 rfc2047_decode_adrlist (env->from);
2187 rfc2047_decode_adrlist (env->reply_to);
2188 rfc2047_decode (&env->subject);
2191 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2192 const char *resent_from, address_t * env_from)
2196 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2197 MESSAGE *msg = NULL;
2200 /* Try to bounce each message out, aborting if we get any failures. */
2201 for (i = 0; i < Context->msgcount; i++)
2202 if (Context->hdrs[i]->tagged)
2204 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2209 /* If we failed to open a message, return with error */
2210 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2216 mutt_mktemp (tempfile);
2217 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2218 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2220 if (!option (OPTBOUNCEDELIVERED))
2221 ch_flags |= CH_WEED_DELIVERED;
2223 fseeko (fp, h->offset, 0);
2224 fprintf (f, "Resent-From: %s", resent_from);
2225 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2226 if (MsgIdFormat && *MsgIdFormat)
2227 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2228 fputs ("Resent-To: ", f);
2229 mutt_write_address_list (to, f, 11, 0);
2230 mutt_copy_header (fp, h, f, ch_flags, NULL);
2232 mutt_copy_bytes (fp, f, h->content->length);
2235 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2236 h->content->encoding == ENC8BIT);
2240 mx_close_message (&msg);
2245 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2248 const char *fqdn = mutt_fqdn (1);
2249 char resent_from[STRING];
2253 resent_from[0] = '\0';
2254 from = mutt_default_from ();
2257 rfc822_qualify (from, fqdn);
2259 rfc2047_encode_adrlist (from, "Resent-From");
2260 if (mutt_addrlist_to_idna (from, &err)) {
2261 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2264 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2267 unset_option (OPTNEWSSEND);
2270 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2272 address_delete (&from);
2278 /* given a list of addresses, return a list of unique addresses */
2279 address_t *mutt_remove_duplicates (address_t * addr)
2281 address_t *top = addr;
2282 address_t **last = ⊤
2287 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2288 if (tmp->mailbox && addr->mailbox &&
2289 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2296 debug_print (2, ("Removing %s\n", addr->mailbox));
2301 address_delete (&addr);
2314 static void set_noconv_flags (BODY * b, short flag)
2316 for (; b; b = b->next) {
2317 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2318 set_noconv_flags (b->parts, flag);
2319 else if (b->type == TYPETEXT && b->noconv) {
2321 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2323 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2328 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2329 int post, char *fcc)
2333 char tempfile[_POSIX_PATH_MAX];
2334 FILE *tempfp = NULL;
2338 set_noconv_flags (hdr->content, 1);
2340 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2341 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2345 /* We need to add a Content-Length field to avoid problems where a line in
2346 * the message body begins with "From "
2348 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2349 mutt_mktemp (tempfile);
2350 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2351 mutt_perror (tempfile);
2352 mx_close_mailbox (&f, NULL);
2357 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2358 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2359 mx_close_mailbox (&f, NULL);
2363 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2364 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2366 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2369 /* (postponment) if this was a reply of some sort, <msgid> contians the
2370 * Message-ID: of message replied to. Save it using a special X-Mutt-
2371 * header so it can be picked up if the message is recalled at a later
2372 * point in time. This will allow the message to be marked as replied if
2373 * the same mailbox is still open.
2376 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2378 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2379 * it can be picked up when the message is recalled
2382 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2383 fprintf (msg->fp, "Status: RO\n");
2387 /* (postponment) if the mail is to be signed or encrypted, save this info */
2388 if (post && (hdr->security & APPLICATION_PGP)) {
2389 fputs ("X-Mutt-PGP: ", msg->fp);
2390 if (hdr->security & ENCRYPT)
2391 fputc ('E', msg->fp);
2392 if (hdr->security & SIGN) {
2393 fputc ('S', msg->fp);
2394 if (PgpSignAs && *PgpSignAs)
2395 fprintf (msg->fp, "<%s>", PgpSignAs);
2397 if (hdr->security & INLINE)
2398 fputc ('I', msg->fp);
2399 fputc ('\n', msg->fp);
2402 /* (postponment) if the mail is to be signed or encrypted, save this info */
2403 if (post && (hdr->security & APPLICATION_SMIME)) {
2404 fputs ("X-Mutt-SMIME: ", msg->fp);
2405 if (hdr->security & ENCRYPT) {
2406 fputc ('E', msg->fp);
2407 if (SmimeCryptAlg && *SmimeCryptAlg)
2408 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2410 if (hdr->security & SIGN) {
2411 fputc ('S', msg->fp);
2412 if (SmimeDefaultKey && *SmimeDefaultKey)
2413 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2415 if (hdr->security & INLINE)
2416 fputc ('I', msg->fp);
2417 fputc ('\n', msg->fp);
2421 /* (postponement) if the mail is to be sent through a mixmaster
2422 * chain, save that information
2425 if (post && hdr->chain && hdr->chain) {
2428 fputs ("X-Mutt-Mix:", msg->fp);
2429 for (p = hdr->chain; p; p = p->next)
2430 fprintf (msg->fp, " %s", (char *) p->data);
2432 fputc ('\n', msg->fp);
2437 char sasha[LONG_STRING];
2440 mutt_write_mime_body (hdr->content, tempfp);
2442 /* make sure the last line ends with a newline. Emacs doesn't ensure
2443 * this will happen, and it can cause problems parsing the mailbox
2446 fseeko (tempfp, -1, 2);
2447 if (fgetc (tempfp) != '\n') {
2448 fseeko (tempfp, 0, 2);
2449 fputc ('\n', tempfp);
2453 if (ferror (tempfp)) {
2454 debug_print (1, ("%s: write failed.\n", tempfile));
2457 mx_commit_message (msg, &f); /* XXX - really? */
2458 mx_close_message (&msg);
2459 mx_close_mailbox (&f, NULL);
2463 /* count the number of lines */
2465 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2467 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2468 fprintf (msg->fp, "Lines: %d\n\n", lines);
2470 /* copy the body and clean up */
2472 r = mutt_copy_stream (tempfp, msg->fp);
2473 if (fclose (tempfp) != 0)
2475 /* if there was an error, leave the temp version */
2480 fputc ('\n', msg->fp); /* finish off the header */
2481 r = mutt_write_mime_body (hdr->content, msg->fp);
2484 if (mx_commit_message (msg, &f) != 0)
2486 mx_close_message (&msg);
2487 mx_close_mailbox (&f, NULL);
2490 set_noconv_flags (hdr->content, 0);