2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
5 * This file is part of mutt-ng, see http://www.muttng.org/.
6 * It's licensed under the GNU General Public License,
7 * please see the file GPL in the top level source directory.
16 #include <lib-lib/mem.h>
17 #include <lib-lib/ascii.h>
18 #include <lib-lib/str.h>
19 #include <lib-lib/macros.h>
20 #include <lib-lib/file.h>
22 #include <lib-mime/mime.h>
26 #include "recvattach.h"
27 #include "mutt_curses.h"
33 #include "mutt_crypt.h"
34 #include "mutt_idna.h"
36 #include "lib/debug.h"
47 #include <sys/utsname.h>
50 # include "mutt_libesmtp.h"
51 #endif /* USE_LIBESMTP */
57 #ifdef HAVE_SYSEXITS_H
59 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
63 /* If you are debugging this file, comment out the following line. */
72 #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_cat (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_cat (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) \
368 || ((WithCrypto & APPLICATION_PGP)\
369 && 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 ((WithCrypto & APPLICATION_PGP)
403 && a->type == TYPEAPPLICATION
404 && m_strcmp(a->subtype, "pgp-encrypted") == 0) {
405 fputs ("Version: 1\n", f);
409 if ((fpin = fopen (a->filename, "r")) == NULL) {
410 debug_print (1, ("%s no longer exists!\n", a->filename));
411 mutt_error (_("%s no longer exists!"), a->filename);
415 if (a->type == TYPETEXT && (!a->noconv))
416 fc = fgetconv_open (fpin, a->file_charset,
417 mutt_get_body_charset (send_charset,
418 sizeof (send_charset), a), 0);
420 fc = fgetconv_open (fpin, 0, 0, 0);
422 if (a->encoding == ENCQUOTEDPRINTABLE)
423 encode_quoted (fc, f, write_as_text_part (a));
424 else if (a->encoding == ENCBASE64)
425 encode_base64 (fc, f, write_as_text_part (a));
426 else if (a->type == TYPETEXT && (!a->noconv))
427 encode_8bit (fc, f, write_as_text_part (a));
429 mutt_copy_stream (fpin, f);
431 fgetconv_close (&fc);
434 return (ferror (f) ? -1 : 0);
437 #undef write_as_text_part
439 #define BOUNDARYLEN 16
440 void mutt_generate_boundary (PARAMETER ** parm)
442 char rs[BOUNDARYLEN + 1];
447 for (i = 0; i < BOUNDARYLEN; i++)
448 *p++ = __m_b64chars[LRAND() % sizeof(__m_b64chars)];
451 mutt_set_parameter ("boundary", rs, parm);
463 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
467 int whitespace = s->whitespace;
469 int linelen = s->linelen;
470 int was_cr = s->was_cr;
472 if (!d) { /* This signals EOF */
475 if (linelen > info->linemax)
476 info->linemax = linelen;
481 for (; dlen; d++, dlen--) {
494 if (linelen > info->linemax)
495 info->linemax = linelen;
510 if (linelen > info->linemax)
511 info->linemax = linelen;
516 else if (ch == '\r') {
524 else if (ch == '\t' || ch == '\f') {
528 else if (ch < 32 || ch == 127)
532 if ((ch == 'F') || (ch == 'f'))
542 if (linelen == 2 && ch != 'r')
544 else if (linelen == 3 && ch != 'o')
546 else if (linelen == 4) {
559 if (ch != ' ' && ch != '\t')
564 s->whitespace = whitespace;
566 s->linelen = linelen;
571 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
572 #define BUGGY_ICONV 1
575 * Find the best charset conversion of the file from fromcode into one
576 * of the tocodes. If successful, set *tocode and CONTENT *info and
577 * return the number of characters converted inexactly. If no
578 * conversion was possible, return -1.
580 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
581 * which would otherwise prevent us from knowing the number of inexact
582 * conversions. Where the candidate target charset is UTF-8 we avoid
583 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
584 * fails with some libraries.
586 * We assume that the output from iconv is never more than 4 times as
587 * long as the input for any pair of charsets we might be interested
590 static size_t convert_file_to (FILE * file, const char *fromcode,
591 int ncodes, const char **tocodes,
592 int *tocode, CONTENT * info)
596 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
599 size_t ibl, obl, ubl, ubl1, n, ret;
602 CONTENT_STATE *states;
605 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
606 if (cd1 == (iconv_t) (-1))
609 cd = p_new(iconv_t, ncodes);
610 score = p_new(size_t, ncodes);
611 states = p_new(CONTENT_STATE, ncodes);
612 infos = p_new(CONTENT, ncodes);
614 for (i = 0; i < ncodes; i++)
615 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
616 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
618 /* Special case for conversion to UTF-8 */
619 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
625 /* Try to fill input buffer */
626 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
629 /* Convert to UTF-8 */
631 ob = bufu, obl = sizeof (bufu);
632 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
633 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
634 if (n == (size_t) (-1) &&
635 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
636 assert (errno == EILSEQ ||
637 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
643 /* Convert from UTF-8 */
644 for (i = 0; i < ncodes; i++)
645 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
646 ub = bufu, ubl = ubl1;
647 ob = bufo, obl = sizeof (bufo);
648 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
649 if (n == (size_t) (-1)) {
650 assert (errno == E2BIG ||
651 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
652 score[i] = (size_t) (-1);
656 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
659 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
660 /* Special case for conversion to UTF-8 */
661 update_content_info (&infos[i], &states[i], bufu, ubl1);
664 /* Save unused input */
665 memmove (bufi, ib, ibl);
666 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
673 /* Find best score */
675 for (i = 0; i < ncodes; i++) {
676 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
677 /* Special case for conversion to UTF-8 */
682 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
684 else if (ret == (size_t) (-1) || score[i] < ret) {
691 if (ret != (size_t) (-1)) {
692 memcpy (info, &infos[*tocode], sizeof (CONTENT));
693 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
697 for (i = 0; i < ncodes; i++)
698 if (cd[i] != (iconv_t) (-1))
710 #endif /* !HAVE_ICONV */
714 * Find the first of the fromcodes that gives a valid conversion and
715 * the best charset conversion of the file into one of the tocodes. If
716 * successful, set *fromcode and *tocode to dynamically allocated
717 * strings, set CONTENT *info, and return the number of characters
718 * converted inexactly. If no conversion was possible, return -1.
720 * Both fromcodes and tocodes may be colon-separated lists of charsets.
721 * However, if fromcode is zero then fromcodes is assumed to be the
722 * name of a single charset even if it contains a colon.
724 static size_t convert_file_from_to (FILE * file,
725 const char *fromcodes,
726 const char *tocodes, char **fromcode,
727 char **tocode, CONTENT * info)
735 /* Count the tocodes */
737 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
738 if ((c1 = strchr (c, ':')) == c)
744 tcode = p_new(char *, ncodes);
745 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
746 if ((c1 = strchr (c, ':')) == c)
748 tcode[i] = str_substrdup (c, c1);
753 /* Try each fromcode in turn */
754 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
755 if ((c1 = strchr (c, ':')) == c)
757 fcode = str_substrdup (c, c1);
759 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
761 if (ret != (size_t) (-1)) {
771 /* There is only one fromcode */
772 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
774 if (ret != (size_t) (-1)) {
781 for (i = 0; i < ncodes; i++)
790 * Analyze the contents of a file to determine which MIME encoding to use.
791 * Also set the body charset, sometimes, or not.
793 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
798 char *fromcode = NULL;
809 if (stat (fname, &sb) == -1) {
810 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
814 if (!S_ISREG (sb.st_mode)) {
815 mutt_error (_("%s isn't a regular file."), fname);
819 if ((fp = fopen (fname, "r")) == NULL) {
820 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
824 info = p_new(CONTENT, 1);
827 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
828 char *chs = mutt_get_parameter ("charset", b->parameter);
829 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
830 FileCharset : Charset) : Charset;
831 if (Charset && (chs || SendCharset) &&
832 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
833 &fromcode, &tocode, info) != (size_t) (-1)) {
835 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
836 mutt_set_parameter ("charset", chsbuf, &b->parameter);
838 b->file_charset = fromcode;
846 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
847 update_content_info (info, &state, buffer, r);
848 update_content_info (info, &state, 0, 0);
852 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
853 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
855 && !mutt_is_us_ascii (Charset) ? Charset :
856 "unknown-8bit"), &b->parameter);
861 /* Given a file with path ``s'', see if there is a registered MIME type.
862 * returns the major MIME type, and copies the subtype to ``d''. First look
863 * for ~/.mime.types, then look in a system mime.types if we can find one.
864 * The longest match is used so that we can match `ps.gz' when `gz' also
868 int mutt_lookup_mime_type (BODY * att, const char *path)
872 char buf[LONG_STRING];
873 char subtype[STRING], xtype[STRING];
875 int szf, sze, cur_sze;
883 szf = m_strlen(path);
885 for (count = 0; count < 4; count++) {
887 * can't use strtok() because we use it in an inner loop below, so use
888 * a switch statement here instead.
892 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
895 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/muttng-mime.types");
898 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
901 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
904 debug_print (1, ("Internal error, count = %d.\n", count));
905 goto bye; /* shouldn't happen */
908 if ((f = fopen (buf, "r")) != NULL) {
909 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
910 /* weed out any comments */
911 if ((p = strchr (buf, '#')))
914 /* remove any leading space. */
915 ct = vskipspaces(buf);
917 /* position on the next field in this line */
918 if ((p = strpbrk (ct, " \t")) == NULL)
923 /* cycle through the file extensions */
924 while ((p = strtok (p, " \t\n"))) {
926 if ((sze > cur_sze) && (szf >= sze) &&
927 (m_strcasecmp(path + szf - sze, p) == 0
928 || ascii_strcasecmp (path + szf - sze, p) == 0)
929 && (szf == sze || path[szf - sze - 1] == '.'))
931 /* get the content-type */
933 if ((p = strchr (ct, '/')) == NULL) {
934 /* malformed line, just skip it. */
939 for (q = p; *q && !ISSPACE (*q); q++);
941 str_substrcpy (subtype, p, q, sizeof (subtype));
943 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
944 m_strcpy(xtype, sizeof(xtype), ct);
957 if (type != TYPEOTHER || *xtype != '\0') {
959 str_replace (&att->subtype, subtype);
960 str_replace (&att->xtype, xtype);
966 void mutt_message_to_7bit (BODY * a, FILE * fp)
968 char temp[_POSIX_PATH_MAX];
974 if (!a->filename && fp)
976 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
977 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
982 if (stat (a->filename, &sb) == -1) {
983 mutt_perror ("stat");
986 a->length = sb.st_size;
990 if (!(fpout = safe_fopen (temp, "w+"))) {
991 mutt_perror ("fopen");
995 fseeko (fpin, a->offset, 0);
996 a->parts = mutt_parse_messageRFC822 (fpin, a);
998 transform_to_7bit (a->parts, fpin);
1000 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1001 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1003 fputs ("MIME-Version: 1.0\n", fpout);
1004 mutt_write_mime_header (a->parts, fpout);
1005 fputc ('\n', fpout);
1006 mutt_write_mime_body (a->parts, fpout);
1018 a->encoding = ENC7BIT;
1019 a->d_filename = a->filename;
1020 if (a->filename && a->unlink)
1021 unlink (a->filename);
1022 a->filename = m_strdup(temp);
1024 if (stat (a->filename, &sb) == -1) {
1025 mutt_perror ("stat");
1028 a->length = sb.st_size;
1029 mutt_free_body (&a->parts);
1030 a->hdr->content = NULL;
1033 static void transform_to_7bit (BODY * a, FILE * fpin)
1035 char buff[_POSIX_PATH_MAX];
1040 for (; a; a = a->next) {
1041 if (a->type == TYPEMULTIPART) {
1042 if (a->encoding != ENC7BIT)
1043 a->encoding = ENC7BIT;
1045 transform_to_7bit (a->parts, fpin);
1047 else if (mutt_is_message_type (a->type, a->subtype)) {
1048 mutt_message_to_7bit (a, fpin);
1052 a->force_charset = 1;
1055 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1056 mutt_perror ("fopen");
1060 mutt_decode_attachment (a, &s);
1062 a->d_filename = a->filename;
1063 a->filename = m_strdup(buff);
1065 if (stat (a->filename, &sb) == -1) {
1066 mutt_perror ("stat");
1069 a->length = sb.st_size;
1071 mutt_update_encoding (a);
1072 if (a->encoding == ENC8BIT)
1073 a->encoding = ENCQUOTEDPRINTABLE;
1074 else if (a->encoding == ENCBINARY)
1075 a->encoding = ENCBASE64;
1080 /* determine which Content-Transfer-Encoding to use */
1081 static void mutt_set_encoding (BODY * b, CONTENT * info)
1083 char send_charset[SHORT_STRING];
1085 if (b->type == TYPETEXT) {
1087 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1088 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1089 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1090 b->encoding = ENCQUOTEDPRINTABLE;
1091 else if (info->hibin)
1092 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1094 b->encoding = ENC7BIT;
1096 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1097 if (info->lobin || info->hibin) {
1098 if (option (OPTALLOW8BIT) && !info->lobin)
1099 b->encoding = ENC8BIT;
1101 mutt_message_to_7bit (b, NULL);
1104 b->encoding = ENC7BIT;
1106 else if (b->type == TYPEAPPLICATION
1107 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1108 b->encoding = ENC7BIT;
1111 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1112 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1115 /* Determine which encoding is smaller */
1116 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1117 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1118 b->encoding = ENCBASE64;
1120 b->encoding = ENCQUOTEDPRINTABLE;
1124 b->encoding = ENC7BIT;
1128 void mutt_stamp_attachment (BODY * a)
1130 a->stamp = time (NULL);
1133 /* Get a body's character set */
1135 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1139 if (b && b->type != TYPETEXT)
1143 p = mutt_get_parameter ("charset", b->parameter);
1146 mutt_canonical_charset (d, dlen, NONULL (p));
1148 m_strcpy(d, dlen, "us-ascii");
1154 /* Assumes called from send mode where BODY->filename points to actual file */
1155 void mutt_update_encoding (BODY * a)
1158 char chsbuff[STRING];
1160 /* override noconv when it's us-ascii */
1161 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1164 if (!a->force_charset && !a->noconv)
1165 mutt_delete_parameter ("charset", &a->parameter);
1167 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1170 mutt_set_encoding (a, info);
1171 mutt_stamp_attachment (a);
1173 p_delete(&a->content);
1178 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1180 char buffer[LONG_STRING];
1183 int cmflags, chflags;
1184 int pgp = WithCrypto ? hdr->security : 0;
1187 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1188 (hdr->security & ENCRYPT)) {
1189 if (!crypt_valid_passphrase (hdr->security))
1194 mutt_mktemp (buffer);
1195 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1198 body = mutt_new_body ();
1199 body->type = TYPEMESSAGE;
1200 body->subtype = m_strdup("rfc822");
1201 body->filename = m_strdup(buffer);
1204 body->disposition = DISPINLINE;
1207 mutt_parse_mime_message (ctx, hdr);
1212 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1213 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1214 chflags |= CH_MIME | CH_TXTPLAIN;
1215 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1216 if ((WithCrypto & APPLICATION_PGP))
1218 if ((WithCrypto & APPLICATION_SMIME))
1219 pgp &= ~SMIMEENCRYPT;
1221 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1222 if ((WithCrypto & APPLICATION_PGP)
1223 && mutt_is_multipart_encrypted (hdr->content)) {
1224 chflags |= CH_MIME | CH_NONEWLINE;
1225 cmflags = M_CM_DECODE_PGP;
1228 else if ((WithCrypto & APPLICATION_PGP)
1229 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1230 chflags |= CH_MIME | CH_TXTPLAIN;
1231 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1234 else if ((WithCrypto & APPLICATION_SMIME)
1235 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1236 chflags |= CH_MIME | CH_TXTPLAIN;
1237 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1238 pgp &= ~SMIMEENCRYPT;
1242 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1247 body->hdr = mutt_new_header ();
1248 body->hdr->offset = 0;
1249 /* we don't need the user headers here */
1250 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1252 body->hdr->security = pgp;
1253 mutt_update_encoding (body);
1254 body->parts = body->hdr->content;
1261 BODY *mutt_make_file_attach (const char *path)
1266 att = mutt_new_body ();
1267 att->filename = m_strdup(path);
1269 /* Attempt to determine the appropriate content-type based on the filename
1276 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1277 path)) != TYPEOTHER || *xbuf != '\0') {
1279 att->subtype = m_strdup(buf);
1280 att->xtype = m_strdup(xbuf);
1285 mutt_lookup_mime_type (att, path);
1289 if ((info = mutt_get_content_info (path, att)) == NULL) {
1290 mutt_free_body (&att);
1294 if (!att->subtype) {
1295 if (info->lobin == 0
1296 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1298 * Statistically speaking, there should be more than 10% "lobin"
1299 * chars if this is really a binary file...
1301 att->type = TYPETEXT;
1302 att->subtype = m_strdup("plain");
1305 att->type = TYPEAPPLICATION;
1306 att->subtype = m_strdup("octet-stream");
1310 mutt_update_encoding (att);
1314 static int get_toplevel_encoding (BODY * a)
1318 for (; a; a = a->next) {
1319 if (a->encoding == ENCBINARY)
1321 else if (a->encoding == ENC8BIT)
1328 BODY *mutt_make_multipart (BODY * b)
1332 new = mutt_new_body ();
1333 new->type = TYPEMULTIPART;
1334 new->subtype = m_strdup("mixed");
1335 new->encoding = get_toplevel_encoding (b);
1336 mutt_generate_boundary (&new->parameter);
1338 new->disposition = DISPINLINE;
1344 /* remove the multipart body if it exists */
1345 BODY *mutt_remove_multipart (BODY * b)
1353 mutt_free_body (&t);
1358 char *mutt_make_date (char *s, size_t len)
1360 time_t t = time (NULL);
1361 struct tm *l = localtime (&t);
1362 time_t tz = mutt_local_tz (t);
1366 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1367 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1368 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1369 (int) tz / 60, (int) abs (tz) % 60);
1373 /* wrapper around mutt_write_address() so we can handle very large
1374 recipient lists without needing a huge temporary buffer in memory */
1375 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1379 char buf[LONG_STRING];
1387 rfc822_write_address (buf, sizeof (buf), adr, display);
1388 len = m_strlen(buf);
1389 if (count && linelen + len > 74) {
1391 linelen = len + 8; /* tab is usually about 8 spaces... */
1394 if (count && adr->mailbox) {
1402 if (!adr->group && adr->next && adr->next->mailbox) {
1412 /* arbitrary number of elements to grow the array by */
1417 /* need to write the list in reverse because they are stored in reverse order
1418 * when parsed to speed up threading
1420 void mutt_write_references (LIST * r, FILE * f)
1423 int refcnt = 0, refmax = 0;
1425 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1426 if (refcnt == refmax)
1427 p_realloc(&ref, refmax += REF_INC);
1431 while (refcnt-- > 0) {
1433 fputs (ref[refcnt]->data, f);
1439 /* Note: all RFC2047 encoding should be done outside of this routine, except
1440 * for the "real name." This will allow this routine to be used more than
1441 * once, if necessary.
1443 * Likewise, all IDN processing should happen outside of this routine.
1445 * mode == 1 => "lite" mode (used for edit_hdrs)
1446 * mode == 0 => normal mode. write full header + MIME headers
1447 * mode == -1 => write just the envelope info (used for postponing messages)
1449 * privacy != 0 => will omit any headers which may identify the user.
1450 * Output generated is suitable for being sent through
1451 * anonymous remailer chains.
1455 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1456 int mode, int privacy)
1458 char buffer[LONG_STRING];
1460 LIST *tmp = env->userhdrs;
1461 int has_agent = 0; /* user defined user-agent header field exists */
1462 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1465 if (!option (OPTNEWSSEND))
1467 if (mode == 0 && !privacy)
1468 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1470 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1472 /* OPTUSEFROM is not consulted here so that we can still write a From:
1473 * field if the user sets it with the `my_hdr' command
1475 if (env->from && !privacy) {
1477 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1478 fprintf (fp, "From: %s\n", buffer);
1483 mutt_write_address_list (env->to, fp, 4, 0);
1487 if (!option (OPTNEWSSEND))
1489 if (EDIT_HEADER("To:"))
1490 fputs ("To:\n", fp);
1494 mutt_write_address_list (env->cc, fp, 4, 0);
1498 if (!option (OPTNEWSSEND))
1500 if (EDIT_HEADER("Cc:"))
1501 fputs ("Cc:\n", fp);
1504 if (mode != 0 || option (OPTWRITEBCC)) {
1505 fputs ("Bcc: ", fp);
1506 mutt_write_address_list (env->bcc, fp, 5, 0);
1511 if (!option (OPTNEWSSEND))
1513 if (EDIT_HEADER("Bcc:"))
1514 fputs ("Bcc:\n", fp);
1517 if (env->newsgroups)
1518 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1519 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1520 fputs ("Newsgroups:\n", fp);
1522 if (env->followup_to)
1523 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1524 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1525 fputs ("Followup-To:\n", fp);
1527 if (env->x_comment_to)
1528 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1529 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1530 EDIT_HEADER("X-Comment-To:"))
1531 fputs ("X-Comment-To:\n", fp);
1535 fprintf (fp, "Subject: %s\n", env->subject);
1536 else if (mode == 1 && EDIT_HEADER("Subject:"))
1537 fputs ("Subject:\n", fp);
1539 /* save message id if the user has set it */
1540 if (env->message_id && !privacy)
1541 fprintf (fp, "Message-ID: %s\n", env->message_id);
1543 if (env->reply_to) {
1544 fputs ("Reply-To: ", fp);
1545 mutt_write_address_list (env->reply_to, fp, 10, 0);
1547 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1548 fputs ("Reply-To:\n", fp);
1550 if (env->mail_followup_to)
1552 if (!option (OPTNEWSSEND))
1555 fputs ("Mail-Followup-To: ", fp);
1556 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1560 if (env->references) {
1561 fputs ("References:", fp);
1562 mutt_write_references (env->references, fp);
1566 /* Add the MIME headers */
1567 fputs ("MIME-Version: 1.0\n", fp);
1568 mutt_write_mime_header (attach, fp);
1571 if (env->in_reply_to) {
1572 fputs ("In-Reply-To:", fp);
1573 mutt_write_references (env->in_reply_to, fp);
1579 /* Add any user defined headers */
1580 for (; tmp; tmp = tmp->next) {
1581 if ((p = strchr (tmp->data, ':'))) {
1582 p = vskipspaces(p + 1);
1584 continue; /* don't emit empty fields. */
1586 /* check to see if the user has overridden the user-agent field */
1587 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1593 fputs (tmp->data, fp);
1598 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1601 if (OperatingSystem != NULL) {
1602 os = OperatingSystem;
1605 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1607 /* Add a vanity header */
1608 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1611 list_del (&hdrs, (list_del_t*)xmemfree);
1613 return (ferror (fp) == 0 ? 0 : -1);
1616 static void encode_headers (LIST * h)
1622 for (; h; h = h->next) {
1623 if (!(p = strchr (h->data, ':')))
1627 p = vskipspaces(p + 1);
1633 rfc2047_encode_string (&tmp);
1634 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1636 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1642 const char *mutt_fqdn (short may_hide_host)
1646 if (Fqdn && Fqdn[0] != '@') {
1649 if (may_hide_host && option (OPTHIDDENHOST)) {
1650 if ((p = strchr (Fqdn, '.')))
1653 /* sanity check: don't hide the host if
1654 * the fqdn is something like detebe.org.
1657 if (!p || !(q = strchr (p, '.')))
1665 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1666 static char mutt_normalized_char(char c)
1668 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1671 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1673 #define APPEND_FMT(fmt, arg) \
1675 int snlen = snprintf(buf, len, fmt, arg); \
1680 #define APPEND_BYTE(c) \
1697 APPEND_BYTE(mutt_normalized_char(c));
1705 APPEND_FMT("%02d", tm->tm_mday);
1708 APPEND_FMT("%02d", tm->tm_hour);
1711 APPEND_FMT("%02d", tm->tm_mon + 1);
1714 APPEND_FMT("%02d", tm->tm_min);
1717 APPEND_FMT("%lo", (unsigned long)now);
1720 APPEND_FMT("%u", (unsigned int)getpid());
1723 APPEND_FMT("%c", MsgIdPfx);
1724 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1727 APPEND_FMT("%u", (unsigned int)rand());
1730 APPEND_FMT("%x", (unsigned int)rand());
1733 APPEND_FMT("%02d", tm->tm_sec);
1736 APPEND_FMT("%u", (unsigned int) now);
1739 APPEND_FMT("%x", (unsigned int) now);
1741 case 'Y': /* this will break in the year 10000 ;-) */
1742 APPEND_FMT("%04d", tm->tm_year + 1900);
1747 default: /* invalid formats are replaced by '.' */
1749 m_strncat(buf, len, ".", 1);
1759 char *mutt_gen_msgid (void)
1761 char buf[SHORT_STRING];
1762 char localpart[SHORT_STRING];
1763 unsigned int localpart_length;
1766 if (!(fqdn = mutt_fqdn (0)))
1767 fqdn = NONULL (Hostname);
1769 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1771 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1773 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1774 return (m_strdup(buf));
1777 static RETSIGTYPE alarm_handler (int sig)
1782 /* invoke sendmail in a subshell
1783 path (in) path to program to execute
1784 args (in) arguments to pass to program
1785 msg (in) temp file containing message to send
1786 tempfile (out) if sendmail is put in the background, this points
1787 to the temporary file containing the stdout of the
1790 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1796 mutt_block_signals_system ();
1799 /* we also don't want to be stopped right now */
1800 sigaddset (&set, SIGTSTP);
1801 sigprocmask (SIG_BLOCK, &set, NULL);
1803 if (SendmailWait >= 0) {
1804 char tmp[_POSIX_PATH_MAX];
1807 *tempfile = m_strdup(tmp);
1810 if ((pid = fork ()) == 0) {
1811 struct sigaction act, oldalrm;
1813 /* save parent's ID before setsid() */
1816 /* we want the delivery to continue even after the main process dies,
1817 * so we put ourselves into another session right away
1821 /* next we close all open files */
1822 #if defined(OPEN_MAX)
1823 for (fd = 0; fd < OPEN_MAX; fd++)
1825 #elif defined(_POSIX_OPEN_MAX)
1826 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1834 /* now the second fork() */
1835 if ((pid = fork ()) == 0) {
1836 /* "msg" will be opened as stdin */
1837 if (open (msg, O_RDONLY, 0) < 0) {
1843 if (SendmailWait >= 0) {
1844 /* *tempfile will be opened as stdout */
1845 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1848 /* redirect stderr to *tempfile too */
1853 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1855 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1862 else if (pid == -1) {
1868 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1869 * SendmailWait = 0: wait forever
1870 * SendmailWait < 0: don't wait
1872 if (SendmailWait > 0) {
1874 act.sa_handler = alarm_handler;
1876 /* need to make sure waitpid() is interrupted on SIGALRM */
1877 act.sa_flags = SA_INTERRUPT;
1881 sigemptyset (&act.sa_mask);
1882 sigaction (SIGALRM, &act, &oldalrm);
1883 alarm (SendmailWait);
1885 else if (SendmailWait < 0)
1886 _exit (0xff & EX_OK);
1888 if (waitpid (pid, &st, 0) > 0) {
1889 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1890 if (SendmailWait && st == (0xff & EX_OK)) {
1891 unlink (*tempfile); /* no longer needed */
1896 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1897 if (SendmailWait > 0) {
1903 /* reset alarm; not really needed, but... */
1905 sigaction (SIGALRM, &oldalrm, NULL);
1907 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1908 /* the parent is already dead */
1916 sigprocmask (SIG_UNBLOCK, &set, NULL);
1918 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1919 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1921 st = S_ERR; /* error */
1923 mutt_unblock_signals_system (1);
1928 static const char **
1929 add_args(const char **args, size_t *argslen, size_t *argsmax, address_t * addr)
1931 for (; addr; addr = addr->next) {
1932 /* weed out group mailboxes, since those are for display only */
1933 if (addr->mailbox && !addr->group) {
1934 if (*argslen == *argsmax)
1935 p_realloc(&args, *argsmax += 5);
1936 args[(*argslen)++] = addr->mailbox;
1942 static const char **
1943 add_option(const char **args, size_t *argslen, size_t *argsmax, const char *s)
1945 if (*argslen == *argsmax) {
1946 p_realloc(&args, *argsmax += 5);
1948 args[(*argslen)++] = s;
1952 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1953 address_t * to, address_t * cc, address_t * bcc, /* recips */
1954 const char *msg, /* file containing message */
1956 { /* message contains 8bit chars */
1957 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1958 const char **args = NULL;
1959 size_t argslen = 0, argsmax = 0;
1963 if (option (OPTNEWSSEND)) {
1964 char cmd[LONG_STRING];
1966 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1969 i = nntp_post (msg);
1978 s = m_strdup(Sendmail);
1982 while ((ps = strtok (ps, " "))) {
1983 if (argslen == argsmax)
1984 p_realloc(&args, argsmax += 5);
1987 args[argslen++] = ps;
1989 path = m_strdup(ps);
1990 ps = strrchr (ps, '/');
1995 args[argslen++] = ps;
2002 if (!option (OPTNEWSSEND)) {
2004 if (eightbit && option (OPTUSE8BITMIME))
2005 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
2007 if (option (OPTENVFROM)) {
2008 address_t *f = NULL;
2011 else if (from && !from->next)
2014 args = add_option (args, &argslen, &argsmax, "-f");
2015 args = add_args (args, &argslen, &argsmax, f);
2019 args = add_option (args, &argslen, &argsmax, "-N");
2020 args = add_option (args, &argslen, &argsmax, DsnNotify);
2023 args = add_option (args, &argslen, &argsmax, "-R");
2024 args = add_option (args, &argslen, &argsmax, DsnReturn);
2026 args = add_option (args, &argslen, &argsmax, "--");
2027 args = add_args (args, &argslen, &argsmax, to);
2028 args = add_args (args, &argslen, &argsmax, cc);
2029 args = add_args (args, &argslen, &argsmax, bcc);
2034 if (argslen == argsmax)
2035 p_realloc(&args, ++argsmax);
2037 args[argslen++] = NULL;
2039 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2041 const char *e = mutt_strsysexit (i);
2043 e = mutt_strsysexit (i);
2044 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2049 if (stat (childout, &st) == 0 && st.st_size > 0)
2050 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2058 p_delete(&childout);
2063 if (i == (EX_OK & 0xff))
2065 else if (i == S_BKG)
2072 int mutt_invoke_mta (address_t * from, /* the sender */
2073 address_t * to, address_t * cc, address_t * bcc, /* recips */
2074 const char *msg, /* file containing message */
2076 { /* message contains 8bit chars */
2079 if (!option (OPTNEWSSEND))
2082 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2085 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2088 /* appends string 'b' to string 'a', and returns the pointer to the new
2090 char *mutt_append_string (char *a, const char *b)
2092 size_t la = m_strlen(a);
2094 p_realloc(&a, la + m_strlen(b) + 1);
2095 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2099 /* returns 1 if char `c' needs to be quoted to protect from shell
2100 interpretation when executing commands in a subshell */
2101 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2103 /* returns 1 if string `s' contains characters which could cause problems
2104 when used on a command line to execute a command */
2105 int mutt_needs_quote (const char *s)
2108 if (INVALID_CHAR (*s))
2115 /* Quote a string to prevent shell escapes when this string is used on the
2116 command line to send mail. */
2117 char *mutt_quote_string (const char *s)
2122 rlen = m_strlen(s) + 3;
2123 pr = r = p_new(char, rlen);
2126 if (INVALID_CHAR (*s)) {
2129 p_realloc(&r, ++rlen);
2140 /* For postponing (!final) do the necessary encodings only */
2141 void mutt_prepare_envelope (ENVELOPE * env, int final)
2143 char buffer[LONG_STRING];
2146 if (env->bcc && !(env->to || env->cc)) {
2147 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2148 * recipients if there is no To: or Cc: field, so attempt to suppress
2149 * it by using an empty To: field.
2151 env->to = rfc822_new_address ();
2153 env->to->next = rfc822_new_address ();
2156 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2159 env->to->mailbox = m_strdup(buffer);
2162 mutt_set_followup_to (env);
2164 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2165 env->message_id = mutt_gen_msgid ();
2168 /* Take care of 8-bit => 7-bit conversion. */
2169 rfc2047_encode_adrlist (env->to, "To");
2170 rfc2047_encode_adrlist (env->cc, "Cc");
2171 rfc2047_encode_adrlist (env->bcc, "Bcc");
2172 rfc2047_encode_adrlist (env->from, "From");
2173 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2174 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2178 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2181 rfc2047_encode_string (&env->subject);
2183 encode_headers (env->userhdrs);
2186 void mutt_unprepare_envelope (ENVELOPE * env)
2190 for (item = env->userhdrs; item; item = item->next)
2191 rfc2047_decode (&item->data);
2193 rfc822_free_address (&env->mail_followup_to);
2195 /* back conversions */
2196 rfc2047_decode_adrlist (env->to);
2197 rfc2047_decode_adrlist (env->cc);
2198 rfc2047_decode_adrlist (env->bcc);
2199 rfc2047_decode_adrlist (env->from);
2200 rfc2047_decode_adrlist (env->reply_to);
2201 rfc2047_decode (&env->subject);
2204 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2205 const char *resent_from, address_t * env_from)
2209 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2210 MESSAGE *msg = NULL;
2213 /* Try to bounce each message out, aborting if we get any failures. */
2214 for (i = 0; i < Context->msgcount; i++)
2215 if (Context->hdrs[i]->tagged)
2217 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2222 /* If we failed to open a message, return with error */
2223 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2229 mutt_mktemp (tempfile);
2230 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2231 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2233 if (!option (OPTBOUNCEDELIVERED))
2234 ch_flags |= CH_WEED_DELIVERED;
2236 fseeko (fp, h->offset, 0);
2237 fprintf (f, "Resent-From: %s", resent_from);
2238 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2239 if (MsgIdFormat && *MsgIdFormat)
2240 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2241 fputs ("Resent-To: ", f);
2242 mutt_write_address_list (to, f, 11, 0);
2243 mutt_copy_header (fp, h, f, ch_flags, NULL);
2245 mutt_copy_bytes (fp, f, h->content->length);
2248 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2249 h->content->encoding == ENC8BIT);
2253 mx_close_message (&msg);
2258 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2261 const char *fqdn = mutt_fqdn (1);
2262 char resent_from[STRING];
2266 resent_from[0] = '\0';
2267 from = mutt_default_from ();
2270 rfc822_qualify (from, fqdn);
2272 rfc2047_encode_adrlist (from, "Resent-From");
2273 if (mutt_addrlist_to_idna (from, &err)) {
2274 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2277 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2280 unset_option (OPTNEWSSEND);
2283 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2285 rfc822_free_address (&from);
2291 /* given a list of addresses, return a list of unique addresses */
2292 address_t *mutt_remove_duplicates (address_t * addr)
2294 address_t *top = addr;
2295 address_t **last = ⊤
2300 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2301 if (tmp->mailbox && addr->mailbox &&
2302 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2309 debug_print (2, ("Removing %s\n", addr->mailbox));
2314 rfc822_free_address (&addr);
2327 static void set_noconv_flags (BODY * b, short flag)
2329 for (; b; b = b->next) {
2330 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2331 set_noconv_flags (b->parts, flag);
2332 else if (b->type == TYPETEXT && b->noconv) {
2334 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2336 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2341 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2342 int post, char *fcc)
2346 char tempfile[_POSIX_PATH_MAX];
2347 FILE *tempfp = NULL;
2351 set_noconv_flags (hdr->content, 1);
2353 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2354 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2358 /* We need to add a Content-Length field to avoid problems where a line in
2359 * the message body begins with "From "
2361 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2362 mutt_mktemp (tempfile);
2363 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2364 mutt_perror (tempfile);
2365 mx_close_mailbox (&f, NULL);
2370 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2371 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2372 mx_close_mailbox (&f, NULL);
2376 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2377 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2379 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2382 /* (postponment) if this was a reply of some sort, <msgid> contians the
2383 * Message-ID: of message replied to. Save it using a special X-Mutt-
2384 * header so it can be picked up if the message is recalled at a later
2385 * point in time. This will allow the message to be marked as replied if
2386 * the same mailbox is still open.
2389 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2391 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2392 * it can be picked up when the message is recalled
2395 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2396 fprintf (msg->fp, "Status: RO\n");
2400 /* (postponment) if the mail is to be signed or encrypted, save this info */
2401 if ((WithCrypto & APPLICATION_PGP)
2402 && post && (hdr->security & APPLICATION_PGP)) {
2403 fputs ("X-Mutt-PGP: ", msg->fp);
2404 if (hdr->security & ENCRYPT)
2405 fputc ('E', msg->fp);
2406 if (hdr->security & SIGN) {
2407 fputc ('S', msg->fp);
2408 if (PgpSignAs && *PgpSignAs)
2409 fprintf (msg->fp, "<%s>", PgpSignAs);
2411 if (hdr->security & INLINE)
2412 fputc ('I', msg->fp);
2413 fputc ('\n', msg->fp);
2416 /* (postponment) if the mail is to be signed or encrypted, save this info */
2417 if ((WithCrypto & APPLICATION_SMIME)
2418 && post && (hdr->security & APPLICATION_SMIME)) {
2419 fputs ("X-Mutt-SMIME: ", msg->fp);
2420 if (hdr->security & ENCRYPT) {
2421 fputc ('E', msg->fp);
2422 if (SmimeCryptAlg && *SmimeCryptAlg)
2423 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2425 if (hdr->security & SIGN) {
2426 fputc ('S', msg->fp);
2427 if (SmimeDefaultKey && *SmimeDefaultKey)
2428 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2430 if (hdr->security & INLINE)
2431 fputc ('I', msg->fp);
2432 fputc ('\n', msg->fp);
2436 /* (postponement) if the mail is to be sent through a mixmaster
2437 * chain, save that information
2440 if (post && hdr->chain && hdr->chain) {
2443 fputs ("X-Mutt-Mix:", msg->fp);
2444 for (p = hdr->chain; p; p = p->next)
2445 fprintf (msg->fp, " %s", (char *) p->data);
2447 fputc ('\n', msg->fp);
2452 char sasha[LONG_STRING];
2455 mutt_write_mime_body (hdr->content, tempfp);
2457 /* make sure the last line ends with a newline. Emacs doesn't ensure
2458 * this will happen, and it can cause problems parsing the mailbox
2461 fseeko (tempfp, -1, 2);
2462 if (fgetc (tempfp) != '\n') {
2463 fseeko (tempfp, 0, 2);
2464 fputc ('\n', tempfp);
2468 if (ferror (tempfp)) {
2469 debug_print (1, ("%s: write failed.\n", tempfile));
2472 mx_commit_message (msg, &f); /* XXX - really? */
2473 mx_close_message (&msg);
2474 mx_close_mailbox (&f, NULL);
2478 /* count the number of lines */
2480 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2482 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2483 fprintf (msg->fp, "Lines: %d\n\n", lines);
2485 /* copy the body and clean up */
2487 r = mutt_copy_stream (tempfp, msg->fp);
2488 if (fclose (tempfp) != 0)
2490 /* if there was an error, leave the temp version */
2495 fputc ('\n', msg->fp); /* finish off the header */
2496 r = mutt_write_mime_body (hdr->content, msg->fp);
2499 if (mx_commit_message (msg, &f) != 0)
2501 mx_close_message (&msg);
2502 mx_close_mailbox (&f, NULL);
2505 set_noconv_flags (hdr->content, 0);