2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
5 * This file is part of mutt-ng, see http://www.muttng.org/.
6 * It's licensed under the GNU General Public License,
7 * please see the file GPL in the top level source directory.
25 #include <sys/utsname.h>
27 #include <lib-lib/mem.h>
28 #include <lib-lib/ascii.h>
29 #include <lib-lib/str.h>
30 #include <lib-lib/macros.h>
31 #include <lib-lib/file.h>
32 #include <lib-lib/debug.h>
34 #include <lib-sys/exit.h>
36 #include <lib-mime/mime.h>
38 #include <lib-ui/curses.h>
42 #include "recvattach.h"
47 #include <lib-crypt/crypt.h>
48 #include "mutt_idna.h"
51 # include "mutt_libesmtp.h"
52 #endif /* USE_LIBESMTP */
58 #ifdef HAVE_SYSEXITS_H
60 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
64 /* If you are debugging this file, comment out the following line. */
73 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
75 static char MsgIdPfx = 'A';
77 static void transform_to_7bit (BODY * a, FILE * fpin);
79 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
82 char line[77], savechar;
84 while ((c = fgetconv (fc)) != EOF) {
85 /* Wrap the line if needed. */
86 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
87 /* If the last character is "quoted", then be sure to move all three
88 * characters to the next line. Otherwise, just move the last
91 if (line[linelen - 3] == '=') {
92 line[linelen - 3] = 0;
97 line[1] = line[linelen - 2];
98 line[2] = line[linelen - 1];
102 savechar = line[linelen - 1];
103 line[linelen - 1] = '=';
112 /* Escape lines that begin with/only contain "the message separator". */
113 if (linelen == 4 && !m_strncmp("From", line, 4)) {
114 m_strcpy(line, sizeof(line), "=46rom");
117 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
118 m_strcpy(line, sizeof(line), "=66rom");
121 else if (linelen == 1 && line[0] == '.') {
122 m_strcpy(line, sizeof(line), "=2E");
127 if (c == '\n' && istext) {
128 /* Check to make sure there is no trailing space on this line. */
130 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
132 sprintf (line + linelen - 1, "=%2.2X",
133 (unsigned char) line[linelen - 1]);
137 int savechar = line[linelen - 1];
139 line[linelen - 1] = '=';
142 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
152 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
153 /* Check to make sure there is enough room for the quoted character.
154 * If not, wrap to the next line.
157 line[linelen++] = '=';
163 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
167 /* Don't worry about wrapping the line here. That will happen during
168 * the next iteration when I'll also know what the next character is.
174 /* Take care of anything left in the buffer */
176 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
177 /* take care of trailing whitespace */
179 sprintf (line + linelen - 1, "=%2.2X",
180 (unsigned char) line[linelen - 1]);
182 savechar = line[linelen - 1];
183 line[linelen - 1] = '=';
187 sprintf (line, "=%2.2X", (unsigned char) savechar);
196 static char b64_buffer[3];
197 static short b64_num;
198 static short b64_linelen;
200 static void b64_flush (FILE * fout)
207 if (b64_linelen >= 72) {
212 for (i = b64_num; i < 3; i++)
213 b64_buffer[i] = '\0';
215 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
218 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
223 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
227 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
232 while (b64_linelen % 4) {
241 static void b64_putc (char c, FILE * fout)
246 b64_buffer[b64_num++] = c;
250 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
254 b64_num = b64_linelen = 0;
256 while ((ch = fgetconv (fc)) != EOF) {
257 if (istext && ch == '\n' && ch1 != '\r')
258 b64_putc ('\r', fout);
266 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
270 while ((ch = fgetconv (fc)) != EOF)
275 int mutt_write_mime_header (BODY * a, FILE * f)
285 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
288 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
290 for (p = a->parameter; p; p = p->next) {
299 tmp = m_strdup(p->value);
300 encode = rfc2231_encode_string (&tmp);
301 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
303 /* Dirty hack to make messages readable by Outlook Express
304 * for the Mac: force quotes around the boundary parameter
305 * even when they aren't needed.
308 if (!ascii_strcasecmp (p->attribute, "boundary")
309 && !strcmp (buffer, tmp))
310 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
314 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
316 if (len + tmplen + 2 > 76) {
325 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
333 fprintf (f, "Content-Description: %s\n", a->description);
335 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
338 if (!(fn = a->d_filename))
344 /* Strip off the leading path... */
345 if ((t = strrchr (fn, '/')))
352 encode = rfc2231_encode_string (&tmp);
353 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
355 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
361 if (a->encoding != ENC7BIT)
362 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
364 /* Do NOT add the terminator here!!! */
365 return (ferror (f) ? -1 : 0);
368 # define write_as_text_part(a) (mutt_is_text_part(a) || mutt_is_application_pgp(a))
370 int mutt_write_mime_body (BODY * a, FILE * f)
372 char *p, boundary[SHORT_STRING];
373 char send_charset[SHORT_STRING];
378 if (a->type == TYPEMULTIPART) {
379 /* First, find the boundary to use */
380 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
381 debug_print (1, ("no boundary parameter found!\n"));
382 mutt_error _("No boundary parameter found! [report this error]");
386 m_strcpy(boundary, sizeof(boundary), p);
388 for (t = a->parts; t; t = t->next) {
389 fprintf (f, "\n--%s\n", boundary);
390 if (mutt_write_mime_header (t, f) == -1)
393 if (mutt_write_mime_body (t, f) == -1)
396 fprintf (f, "\n--%s--\n", boundary);
397 return (ferror (f) ? -1 : 0);
400 /* This is pretty gross, but it's the best solution for now... */
401 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
402 fputs ("Version: 1\n", f);
406 if ((fpin = fopen (a->filename, "r")) == NULL) {
407 debug_print (1, ("%s no longer exists!\n", a->filename));
408 mutt_error (_("%s no longer exists!"), a->filename);
412 if (a->type == TYPETEXT && (!a->noconv))
413 fc = fgetconv_open (fpin, a->file_charset,
414 mutt_get_body_charset (send_charset,
415 sizeof (send_charset), a), 0);
417 fc = fgetconv_open (fpin, 0, 0, 0);
419 if (a->encoding == ENCQUOTEDPRINTABLE)
420 encode_quoted (fc, f, write_as_text_part (a));
421 else if (a->encoding == ENCBASE64)
422 encode_base64 (fc, f, write_as_text_part (a));
423 else if (a->type == TYPETEXT && (!a->noconv))
424 encode_8bit (fc, f, write_as_text_part (a));
426 mutt_copy_stream (fpin, f);
428 fgetconv_close (&fc);
431 return (ferror (f) ? -1 : 0);
434 #undef write_as_text_part
436 #define BOUNDARYLEN 16
437 void mutt_generate_boundary (PARAMETER ** parm)
439 char rs[BOUNDARYLEN + 1];
444 for (i = 0; i < BOUNDARYLEN; i++)
445 *p++ = __m_b64chars[LRAND() % sizeof(__m_b64chars)];
448 mutt_set_parameter ("boundary", rs, parm);
460 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
464 int whitespace = s->whitespace;
466 int linelen = s->linelen;
467 int was_cr = s->was_cr;
469 if (!d) { /* This signals EOF */
472 if (linelen > info->linemax)
473 info->linemax = linelen;
478 for (; dlen; d++, dlen--) {
491 if (linelen > info->linemax)
492 info->linemax = linelen;
507 if (linelen > info->linemax)
508 info->linemax = linelen;
513 else if (ch == '\r') {
521 else if (ch == '\t' || ch == '\f') {
525 else if (ch < 32 || ch == 127)
529 if ((ch == 'F') || (ch == 'f'))
539 if (linelen == 2 && ch != 'r')
541 else if (linelen == 3 && ch != 'o')
543 else if (linelen == 4) {
556 if (ch != ' ' && ch != '\t')
561 s->whitespace = whitespace;
563 s->linelen = linelen;
568 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
569 #define BUGGY_ICONV 1
572 * Find the best charset conversion of the file from fromcode into one
573 * of the tocodes. If successful, set *tocode and CONTENT *info and
574 * return the number of characters converted inexactly. If no
575 * conversion was possible, return -1.
577 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
578 * which would otherwise prevent us from knowing the number of inexact
579 * conversions. Where the candidate target charset is UTF-8 we avoid
580 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
581 * fails with some libraries.
583 * We assume that the output from iconv is never more than 4 times as
584 * long as the input for any pair of charsets we might be interested
587 static size_t convert_file_to (FILE * file, const char *fromcode,
588 int ncodes, const char **tocodes,
589 int *tocode, CONTENT * info)
593 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
596 size_t ibl, obl, ubl, ubl1, n, ret;
599 CONTENT_STATE *states;
602 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
603 if (cd1 == (iconv_t) (-1))
606 cd = p_new(iconv_t, ncodes);
607 score = p_new(size_t, ncodes);
608 states = p_new(CONTENT_STATE, ncodes);
609 infos = p_new(CONTENT, ncodes);
611 for (i = 0; i < ncodes; i++)
612 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
613 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
615 /* Special case for conversion to UTF-8 */
616 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
622 /* Try to fill input buffer */
623 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
626 /* Convert to UTF-8 */
628 ob = bufu, obl = sizeof (bufu);
629 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
630 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
631 if (n == (size_t) (-1) &&
632 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
633 assert (errno == EILSEQ ||
634 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
640 /* Convert from UTF-8 */
641 for (i = 0; i < ncodes; i++)
642 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
643 ub = bufu, ubl = ubl1;
644 ob = bufo, obl = sizeof (bufo);
645 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
646 if (n == (size_t) (-1)) {
647 assert (errno == E2BIG ||
648 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
649 score[i] = (size_t) (-1);
653 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
656 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
657 /* Special case for conversion to UTF-8 */
658 update_content_info (&infos[i], &states[i], bufu, ubl1);
661 /* Save unused input */
662 memmove (bufi, ib, ibl);
663 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
670 /* Find best score */
672 for (i = 0; i < ncodes; i++) {
673 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
674 /* Special case for conversion to UTF-8 */
679 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
681 else if (ret == (size_t) (-1) || score[i] < ret) {
688 if (ret != (size_t) (-1)) {
689 memcpy (info, &infos[*tocode], sizeof (CONTENT));
690 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
694 for (i = 0; i < ncodes; i++)
695 if (cd[i] != (iconv_t) (-1))
707 #endif /* !HAVE_ICONV */
711 * Find the first of the fromcodes that gives a valid conversion and
712 * the best charset conversion of the file into one of the tocodes. If
713 * successful, set *fromcode and *tocode to dynamically allocated
714 * strings, set CONTENT *info, and return the number of characters
715 * converted inexactly. If no conversion was possible, return -1.
717 * Both fromcodes and tocodes may be colon-separated lists of charsets.
718 * However, if fromcode is zero then fromcodes is assumed to be the
719 * name of a single charset even if it contains a colon.
721 static size_t convert_file_from_to (FILE * file,
722 const char *fromcodes,
723 const char *tocodes, char **fromcode,
724 char **tocode, CONTENT * info)
732 /* Count the tocodes */
734 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
735 if ((c1 = strchr (c, ':')) == c)
741 tcode = p_new(char *, ncodes);
742 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
743 if ((c1 = strchr (c, ':')) == c)
745 tcode[i] = m_substrdup(c, c1);
750 /* Try each fromcode in turn */
751 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
752 if ((c1 = strchr (c, ':')) == c)
754 fcode = m_substrdup(c, c1);
756 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
758 if (ret != (size_t) (-1)) {
768 /* There is only one fromcode */
769 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
771 if (ret != (size_t) (-1)) {
778 for (i = 0; i < ncodes; i++)
787 * Analyze the contents of a file to determine which MIME encoding to use.
788 * Also set the body charset, sometimes, or not.
790 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
795 char *fromcode = NULL;
806 if (stat (fname, &sb) == -1) {
807 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
811 if (!S_ISREG (sb.st_mode)) {
812 mutt_error (_("%s isn't a regular file."), fname);
816 if ((fp = fopen (fname, "r")) == NULL) {
817 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
821 info = p_new(CONTENT, 1);
824 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
825 char *chs = mutt_get_parameter ("charset", b->parameter);
826 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
827 FileCharset : Charset) : Charset;
828 if (Charset && (chs || SendCharset) &&
829 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
830 &fromcode, &tocode, info) != (size_t) (-1)) {
832 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
833 mutt_set_parameter ("charset", chsbuf, &b->parameter);
835 b->file_charset = fromcode;
843 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
844 update_content_info (info, &state, buffer, r);
845 update_content_info (info, &state, 0, 0);
849 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
850 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
852 && !mutt_is_us_ascii (Charset) ? Charset :
853 "unknown-8bit"), &b->parameter);
858 /* Given a file with path ``s'', see if there is a registered MIME type.
859 * returns the major MIME type, and copies the subtype to ``d''. First look
860 * for ~/.mime.types, then look in a system mime.types if we can find one.
861 * The longest match is used so that we can match `ps.gz' when `gz' also
865 int mutt_lookup_mime_type (BODY * att, const char *path)
869 char buf[LONG_STRING];
870 char subtype[STRING], xtype[STRING];
872 int szf, sze, cur_sze;
880 szf = m_strlen(path);
882 for (count = 0; count < 4; count++) {
884 * can't use strtok() because we use it in an inner loop below, so use
885 * a switch statement here instead.
889 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
892 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
895 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
898 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
901 debug_print (1, ("Internal error, count = %d.\n", count));
902 goto bye; /* shouldn't happen */
905 if ((f = fopen (buf, "r")) != NULL) {
906 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
907 /* weed out any comments */
908 if ((p = strchr (buf, '#')))
911 /* remove any leading space. */
912 ct = vskipspaces(buf);
914 /* position on the next field in this line */
915 if ((p = strpbrk (ct, " \t")) == NULL)
920 /* cycle through the file extensions */
921 while ((p = strtok (p, " \t\n"))) {
923 if ((sze > cur_sze) && (szf >= sze) &&
924 (m_strcasecmp(path + szf - sze, p) == 0
925 || ascii_strcasecmp (path + szf - sze, p) == 0)
926 && (szf == sze || path[szf - sze - 1] == '.'))
928 /* get the content-type */
930 if ((p = strchr (ct, '/')) == NULL) {
931 /* malformed line, just skip it. */
936 for (q = p; *q && !ISSPACE (*q); q++);
938 m_strncpy(subtype, sizeof(subtype), p, q - p);
940 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
941 m_strcpy(xtype, sizeof(xtype), ct);
954 if (type != TYPEOTHER || *xtype != '\0') {
956 m_strreplace(&att->subtype, subtype);
957 m_strreplace(&att->xtype, xtype);
963 void mutt_message_to_7bit (BODY * a, FILE * fp)
965 char temp[_POSIX_PATH_MAX];
971 if (!a->filename && fp)
973 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
974 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
979 if (stat (a->filename, &sb) == -1) {
980 mutt_perror ("stat");
983 a->length = sb.st_size;
987 if (!(fpout = safe_fopen (temp, "w+"))) {
988 mutt_perror ("fopen");
992 fseeko (fpin, a->offset, 0);
993 a->parts = mutt_parse_messageRFC822 (fpin, a);
995 transform_to_7bit (a->parts, fpin);
997 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
998 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1000 fputs ("MIME-Version: 1.0\n", fpout);
1001 mutt_write_mime_header (a->parts, fpout);
1002 fputc ('\n', fpout);
1003 mutt_write_mime_body (a->parts, fpout);
1015 a->encoding = ENC7BIT;
1016 a->d_filename = a->filename;
1017 if (a->filename && a->unlink)
1018 unlink (a->filename);
1019 a->filename = m_strdup(temp);
1021 if (stat (a->filename, &sb) == -1) {
1022 mutt_perror ("stat");
1025 a->length = sb.st_size;
1026 mutt_free_body (&a->parts);
1027 a->hdr->content = NULL;
1030 static void transform_to_7bit (BODY * a, FILE * fpin)
1032 char buff[_POSIX_PATH_MAX];
1037 for (; a; a = a->next) {
1038 if (a->type == TYPEMULTIPART) {
1039 if (a->encoding != ENC7BIT)
1040 a->encoding = ENC7BIT;
1042 transform_to_7bit (a->parts, fpin);
1044 else if (mutt_is_message_type (a->type, a->subtype)) {
1045 mutt_message_to_7bit (a, fpin);
1049 a->force_charset = 1;
1052 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1053 mutt_perror ("fopen");
1057 mutt_decode_attachment (a, &s);
1059 a->d_filename = a->filename;
1060 a->filename = m_strdup(buff);
1062 if (stat (a->filename, &sb) == -1) {
1063 mutt_perror ("stat");
1066 a->length = sb.st_size;
1068 mutt_update_encoding (a);
1069 if (a->encoding == ENC8BIT)
1070 a->encoding = ENCQUOTEDPRINTABLE;
1071 else if (a->encoding == ENCBINARY)
1072 a->encoding = ENCBASE64;
1077 /* determine which Content-Transfer-Encoding to use */
1078 static void mutt_set_encoding (BODY * b, CONTENT * info)
1080 char send_charset[SHORT_STRING];
1082 if (b->type == TYPETEXT) {
1084 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1085 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1086 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1087 b->encoding = ENCQUOTEDPRINTABLE;
1088 else if (info->hibin)
1089 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1091 b->encoding = ENC7BIT;
1093 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1094 if (info->lobin || info->hibin) {
1095 if (option (OPTALLOW8BIT) && !info->lobin)
1096 b->encoding = ENC8BIT;
1098 mutt_message_to_7bit (b, NULL);
1101 b->encoding = ENC7BIT;
1103 else if (b->type == TYPEAPPLICATION
1104 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1105 b->encoding = ENC7BIT;
1108 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1109 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1112 /* Determine which encoding is smaller */
1113 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1114 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1115 b->encoding = ENCBASE64;
1117 b->encoding = ENCQUOTEDPRINTABLE;
1121 b->encoding = ENC7BIT;
1125 void mutt_stamp_attachment (BODY * a)
1127 a->stamp = time (NULL);
1130 /* Get a body's character set */
1132 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1136 if (b && b->type != TYPETEXT)
1140 p = mutt_get_parameter ("charset", b->parameter);
1143 mutt_canonical_charset (d, dlen, NONULL (p));
1145 m_strcpy(d, dlen, "us-ascii");
1151 /* Assumes called from send mode where BODY->filename points to actual file */
1152 void mutt_update_encoding (BODY * a)
1155 char chsbuff[STRING];
1157 /* override noconv when it's us-ascii */
1158 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1161 if (!a->force_charset && !a->noconv)
1162 mutt_delete_parameter ("charset", &a->parameter);
1164 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1167 mutt_set_encoding (a, info);
1168 mutt_stamp_attachment (a);
1170 p_delete(&a->content);
1175 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1177 char buffer[LONG_STRING];
1180 int cmflags, chflags;
1181 int pgp = hdr->security;
1183 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1184 (hdr->security & ENCRYPT)) {
1185 if (!crypt_valid_passphrase (hdr->security))
1189 mutt_mktemp (buffer);
1190 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1193 body = mutt_new_body ();
1194 body->type = TYPEMESSAGE;
1195 body->subtype = m_strdup("rfc822");
1196 body->filename = m_strdup(buffer);
1199 body->disposition = DISPINLINE;
1202 mutt_parse_mime_message (ctx, hdr);
1207 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1208 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1209 chflags |= CH_MIME | CH_TXTPLAIN;
1210 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1211 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1213 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1214 if (mutt_is_multipart_encrypted (hdr->content)) {
1215 chflags |= CH_MIME | CH_NONEWLINE;
1216 cmflags = M_CM_DECODE_PGP;
1219 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1220 chflags |= CH_MIME | CH_TXTPLAIN;
1221 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1224 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1225 chflags |= CH_MIME | CH_TXTPLAIN;
1226 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1227 pgp &= ~SMIMEENCRYPT;
1231 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1236 body->hdr = header_new();
1237 body->hdr->offset = 0;
1238 /* we don't need the user headers here */
1239 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1240 body->hdr->security = pgp;
1241 mutt_update_encoding (body);
1242 body->parts = body->hdr->content;
1249 BODY *mutt_make_file_attach (const char *path)
1254 att = mutt_new_body ();
1255 att->filename = m_strdup(path);
1257 /* Attempt to determine the appropriate content-type based on the filename
1264 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1265 path)) != TYPEOTHER || *xbuf != '\0') {
1267 att->subtype = m_strdup(buf);
1268 att->xtype = m_strdup(xbuf);
1273 mutt_lookup_mime_type (att, path);
1277 if ((info = mutt_get_content_info (path, att)) == NULL) {
1278 mutt_free_body (&att);
1282 if (!att->subtype) {
1283 if (info->lobin == 0
1284 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1286 * Statistically speaking, there should be more than 10% "lobin"
1287 * chars if this is really a binary file...
1289 att->type = TYPETEXT;
1290 att->subtype = m_strdup("plain");
1293 att->type = TYPEAPPLICATION;
1294 att->subtype = m_strdup("octet-stream");
1298 mutt_update_encoding (att);
1302 static int get_toplevel_encoding (BODY * a)
1306 for (; a; a = a->next) {
1307 if (a->encoding == ENCBINARY)
1309 else if (a->encoding == ENC8BIT)
1316 BODY *mutt_make_multipart (BODY * b)
1320 new = mutt_new_body ();
1321 new->type = TYPEMULTIPART;
1322 new->subtype = m_strdup("mixed");
1323 new->encoding = get_toplevel_encoding (b);
1324 mutt_generate_boundary (&new->parameter);
1326 new->disposition = DISPINLINE;
1332 /* remove the multipart body if it exists */
1333 BODY *mutt_remove_multipart (BODY * b)
1341 mutt_free_body (&t);
1346 char *mutt_make_date (char *s, size_t len)
1348 time_t t = time (NULL);
1349 struct tm *l = localtime (&t);
1350 time_t tz = mutt_local_tz (t);
1354 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1355 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1356 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1357 (int) tz / 60, (int) abs (tz) % 60);
1361 /* wrapper around mutt_write_address() so we can handle very large
1362 recipient lists without needing a huge temporary buffer in memory */
1363 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1367 char buf[LONG_STRING];
1375 rfc822_write_address (buf, sizeof (buf), adr, display);
1376 len = m_strlen(buf);
1377 if (count && linelen + len > 74) {
1379 linelen = len + 8; /* tab is usually about 8 spaces... */
1382 if (count && adr->mailbox) {
1390 if (!adr->group && adr->next && adr->next->mailbox) {
1400 /* arbitrary number of elements to grow the array by */
1405 /* need to write the list in reverse because they are stored in reverse order
1406 * when parsed to speed up threading
1408 void mutt_write_references (LIST * r, FILE * f)
1411 int refcnt = 0, refmax = 0;
1413 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1414 if (refcnt == refmax)
1415 p_realloc(&ref, refmax += REF_INC);
1419 while (refcnt-- > 0) {
1421 fputs (ref[refcnt]->data, f);
1427 /* Note: all RFC2047 encoding should be done outside of this routine, except
1428 * for the "real name." This will allow this routine to be used more than
1429 * once, if necessary.
1431 * Likewise, all IDN processing should happen outside of this routine.
1433 * mode == 1 => "lite" mode (used for edit_hdrs)
1434 * mode == 0 => normal mode. write full header + MIME headers
1435 * mode == -1 => write just the envelope info (used for postponing messages)
1437 * privacy != 0 => will omit any headers which may identify the user.
1438 * Output generated is suitable for being sent through
1439 * anonymous remailer chains.
1443 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1444 int mode, int privacy)
1446 char buffer[LONG_STRING];
1448 LIST *tmp = env->userhdrs;
1449 int has_agent = 0; /* user defined user-agent header field exists */
1450 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1453 if (!option (OPTNEWSSEND))
1455 if (mode == 0 && !privacy)
1456 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1458 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1460 /* OPTUSEFROM is not consulted here so that we can still write a From:
1461 * field if the user sets it with the `my_hdr' command
1463 if (env->from && !privacy) {
1465 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1466 fprintf (fp, "From: %s\n", buffer);
1471 mutt_write_address_list (env->to, fp, 4, 0);
1475 if (!option (OPTNEWSSEND))
1477 if (EDIT_HEADER("To:"))
1478 fputs ("To:\n", fp);
1482 mutt_write_address_list (env->cc, fp, 4, 0);
1486 if (!option (OPTNEWSSEND))
1488 if (EDIT_HEADER("Cc:"))
1489 fputs ("Cc:\n", fp);
1492 if (mode != 0 || option (OPTWRITEBCC)) {
1493 fputs ("Bcc: ", fp);
1494 mutt_write_address_list (env->bcc, fp, 5, 0);
1499 if (!option (OPTNEWSSEND))
1501 if (EDIT_HEADER("Bcc:"))
1502 fputs ("Bcc:\n", fp);
1505 if (env->newsgroups)
1506 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1507 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1508 fputs ("Newsgroups:\n", fp);
1510 if (env->followup_to)
1511 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1512 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1513 fputs ("Followup-To:\n", fp);
1515 if (env->x_comment_to)
1516 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1517 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1518 EDIT_HEADER("X-Comment-To:"))
1519 fputs ("X-Comment-To:\n", fp);
1523 fprintf (fp, "Subject: %s\n", env->subject);
1524 else if (mode == 1 && EDIT_HEADER("Subject:"))
1525 fputs ("Subject:\n", fp);
1527 /* save message id if the user has set it */
1528 if (env->message_id && !privacy)
1529 fprintf (fp, "Message-ID: %s\n", env->message_id);
1531 if (env->reply_to) {
1532 fputs ("Reply-To: ", fp);
1533 mutt_write_address_list (env->reply_to, fp, 10, 0);
1535 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1536 fputs ("Reply-To:\n", fp);
1538 if (env->mail_followup_to)
1540 if (!option (OPTNEWSSEND))
1543 fputs ("Mail-Followup-To: ", fp);
1544 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1548 if (env->references) {
1549 fputs ("References:", fp);
1550 mutt_write_references (env->references, fp);
1554 /* Add the MIME headers */
1555 fputs ("MIME-Version: 1.0\n", fp);
1556 mutt_write_mime_header (attach, fp);
1559 if (env->in_reply_to) {
1560 fputs ("In-Reply-To:", fp);
1561 mutt_write_references (env->in_reply_to, fp);
1567 /* Add any user defined headers */
1568 for (; tmp; tmp = tmp->next) {
1569 if ((p = strchr (tmp->data, ':'))) {
1570 p = vskipspaces(p + 1);
1572 continue; /* don't emit empty fields. */
1574 /* check to see if the user has overridden the user-agent field */
1575 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1581 fputs (tmp->data, fp);
1586 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1589 if (OperatingSystem != NULL) {
1590 os = OperatingSystem;
1593 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1595 /* Add a vanity header */
1596 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1599 list_del (&hdrs, (list_del_t*)xmemfree);
1601 return (ferror (fp) == 0 ? 0 : -1);
1604 static void encode_headers (LIST * h)
1610 for (; h; h = h->next) {
1611 if (!(p = strchr (h->data, ':')))
1615 p = vskipspaces(p + 1);
1621 rfc2047_encode_string (&tmp);
1622 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1624 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1630 const char *mutt_fqdn (short may_hide_host)
1634 if (Fqdn && Fqdn[0] != '@') {
1637 if (may_hide_host && option (OPTHIDDENHOST)) {
1638 if ((p = strchr (Fqdn, '.')))
1641 /* sanity check: don't hide the host if
1642 * the fqdn is something like detebe.org.
1645 if (!p || !(q = strchr (p, '.')))
1653 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1654 static char mutt_normalized_char(char c)
1656 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1659 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1661 #define APPEND_FMT(fmt, arg) \
1663 int snlen = snprintf(buf, len, fmt, arg); \
1668 #define APPEND_BYTE(c) \
1685 APPEND_BYTE(mutt_normalized_char(c));
1693 APPEND_FMT("%02d", tm->tm_mday);
1696 APPEND_FMT("%02d", tm->tm_hour);
1699 APPEND_FMT("%02d", tm->tm_mon + 1);
1702 APPEND_FMT("%02d", tm->tm_min);
1705 APPEND_FMT("%lo", (unsigned long)now);
1708 APPEND_FMT("%u", (unsigned int)getpid());
1711 APPEND_FMT("%c", MsgIdPfx);
1712 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1715 APPEND_FMT("%u", (unsigned int)rand());
1718 APPEND_FMT("%x", (unsigned int)rand());
1721 APPEND_FMT("%02d", tm->tm_sec);
1724 APPEND_FMT("%u", (unsigned int) now);
1727 APPEND_FMT("%x", (unsigned int) now);
1729 case 'Y': /* this will break in the year 10000 ;-) */
1730 APPEND_FMT("%04d", tm->tm_year + 1900);
1735 default: /* invalid formats are replaced by '.' */
1737 m_strncat(buf, len, ".", 1);
1747 char *mutt_gen_msgid (void)
1749 char buf[SHORT_STRING];
1750 char localpart[SHORT_STRING];
1751 unsigned int localpart_length;
1754 if (!(fqdn = mutt_fqdn (0)))
1755 fqdn = NONULL (Hostname);
1757 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1759 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1761 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1762 return (m_strdup(buf));
1765 static RETSIGTYPE alarm_handler (int sig)
1770 /* invoke sendmail in a subshell
1771 path (in) path to program to execute
1772 args (in) arguments to pass to program
1773 msg (in) temp file containing message to send
1774 tempfile (out) if sendmail is put in the background, this points
1775 to the temporary file containing the stdout of the
1778 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1784 mutt_block_signals_system ();
1787 /* we also don't want to be stopped right now */
1788 sigaddset (&set, SIGTSTP);
1789 sigprocmask (SIG_BLOCK, &set, NULL);
1791 if (SendmailWait >= 0) {
1792 char tmp[_POSIX_PATH_MAX];
1795 *tempfile = m_strdup(tmp);
1798 if ((pid = fork ()) == 0) {
1799 struct sigaction act, oldalrm;
1801 /* save parent's ID before setsid() */
1804 /* we want the delivery to continue even after the main process dies,
1805 * so we put ourselves into another session right away
1809 /* next we close all open files */
1810 #if defined(OPEN_MAX)
1811 for (fd = 0; fd < OPEN_MAX; fd++)
1813 #elif defined(_POSIX_OPEN_MAX)
1814 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1822 /* now the second fork() */
1823 if ((pid = fork ()) == 0) {
1824 /* "msg" will be opened as stdin */
1825 if (open (msg, O_RDONLY, 0) < 0) {
1831 if (SendmailWait >= 0) {
1832 /* *tempfile will be opened as stdout */
1833 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1836 /* redirect stderr to *tempfile too */
1841 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1843 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1850 else if (pid == -1) {
1856 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1857 * SendmailWait = 0: wait forever
1858 * SendmailWait < 0: don't wait
1860 if (SendmailWait > 0) {
1862 act.sa_handler = alarm_handler;
1864 /* need to make sure waitpid() is interrupted on SIGALRM */
1865 act.sa_flags = SA_INTERRUPT;
1869 sigemptyset (&act.sa_mask);
1870 sigaction (SIGALRM, &act, &oldalrm);
1871 alarm (SendmailWait);
1873 else if (SendmailWait < 0)
1874 _exit (0xff & EX_OK);
1876 if (waitpid (pid, &st, 0) > 0) {
1877 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1878 if (SendmailWait && st == (0xff & EX_OK)) {
1879 unlink (*tempfile); /* no longer needed */
1884 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1885 if (SendmailWait > 0) {
1891 /* reset alarm; not really needed, but... */
1893 sigaction (SIGALRM, &oldalrm, NULL);
1895 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1896 /* the parent is already dead */
1904 sigprocmask (SIG_UNBLOCK, &set, NULL);
1906 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1907 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1909 st = S_ERR; /* error */
1911 mutt_unblock_signals_system (1);
1916 static const char **
1917 add_args(const char **args, size_t *argslen, size_t *argsmax, address_t * addr)
1919 for (; addr; addr = addr->next) {
1920 /* weed out group mailboxes, since those are for display only */
1921 if (addr->mailbox && !addr->group) {
1922 if (*argslen == *argsmax)
1923 p_realloc(&args, *argsmax += 5);
1924 args[(*argslen)++] = addr->mailbox;
1930 static const char **
1931 add_option(const char **args, size_t *argslen, size_t *argsmax, const char *s)
1933 if (*argslen == *argsmax) {
1934 p_realloc(&args, *argsmax += 5);
1936 args[(*argslen)++] = s;
1940 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1941 address_t * to, address_t * cc, address_t * bcc, /* recips */
1942 const char *msg, /* file containing message */
1944 { /* message contains 8bit chars */
1945 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1946 const char **args = NULL;
1947 size_t argslen = 0, argsmax = 0;
1951 if (option (OPTNEWSSEND)) {
1952 char cmd[LONG_STRING];
1954 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1957 i = nntp_post (msg);
1966 s = m_strdup(Sendmail);
1970 while ((ps = strtok (ps, " "))) {
1971 if (argslen == argsmax)
1972 p_realloc(&args, argsmax += 5);
1975 args[argslen++] = ps;
1977 path = m_strdup(ps);
1978 ps = strrchr (ps, '/');
1983 args[argslen++] = ps;
1990 if (!option (OPTNEWSSEND)) {
1992 if (eightbit && option (OPTUSE8BITMIME))
1993 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1995 if (option (OPTENVFROM)) {
1996 address_t *f = NULL;
1999 else if (from && !from->next)
2002 args = add_option (args, &argslen, &argsmax, "-f");
2003 args = add_args (args, &argslen, &argsmax, f);
2007 args = add_option (args, &argslen, &argsmax, "-N");
2008 args = add_option (args, &argslen, &argsmax, DsnNotify);
2011 args = add_option (args, &argslen, &argsmax, "-R");
2012 args = add_option (args, &argslen, &argsmax, DsnReturn);
2014 args = add_option (args, &argslen, &argsmax, "--");
2015 args = add_args (args, &argslen, &argsmax, to);
2016 args = add_args (args, &argslen, &argsmax, cc);
2017 args = add_args (args, &argslen, &argsmax, bcc);
2022 if (argslen == argsmax)
2023 p_realloc(&args, ++argsmax);
2025 args[argslen++] = NULL;
2027 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2029 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2034 if (stat (childout, &st) == 0 && st.st_size > 0)
2035 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2043 p_delete(&childout);
2048 if (i == (EX_OK & 0xff))
2050 else if (i == S_BKG)
2057 int mutt_invoke_mta (address_t * from, /* the sender */
2058 address_t * to, address_t * cc, address_t * bcc, /* recips */
2059 const char *msg, /* file containing message */
2061 { /* message contains 8bit chars */
2064 if (!option (OPTNEWSSEND))
2067 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2070 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2073 /* appends string 'b' to string 'a', and returns the pointer to the new
2075 char *mutt_append_string (char *a, const char *b)
2077 size_t la = m_strlen(a);
2079 p_realloc(&a, la + m_strlen(b) + 1);
2080 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2084 /* returns 1 if char `c' needs to be quoted to protect from shell
2085 interpretation when executing commands in a subshell */
2086 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2088 /* returns 1 if string `s' contains characters which could cause problems
2089 when used on a command line to execute a command */
2090 int mutt_needs_quote (const char *s)
2093 if (INVALID_CHAR (*s))
2100 /* Quote a string to prevent shell escapes when this string is used on the
2101 command line to send mail. */
2102 char *mutt_quote_string (const char *s)
2107 rlen = m_strlen(s) + 3;
2108 pr = r = p_new(char, rlen);
2111 if (INVALID_CHAR (*s)) {
2114 p_realloc(&r, ++rlen);
2125 /* For postponing (!final) do the necessary encodings only */
2126 void mutt_prepare_envelope (ENVELOPE * env, int final)
2128 char buffer[LONG_STRING];
2131 if (env->bcc && !(env->to || env->cc)) {
2132 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2133 * recipients if there is no To: or Cc: field, so attempt to suppress
2134 * it by using an empty To: field.
2136 env->to = address_new ();
2138 env->to->next = address_new ();
2141 rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
2144 env->to->mailbox = m_strdup(buffer);
2147 mutt_set_followup_to (env);
2149 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2150 env->message_id = mutt_gen_msgid ();
2153 /* Take care of 8-bit => 7-bit conversion. */
2154 rfc2047_encode_adrlist (env->to, "To");
2155 rfc2047_encode_adrlist (env->cc, "Cc");
2156 rfc2047_encode_adrlist (env->bcc, "Bcc");
2157 rfc2047_encode_adrlist (env->from, "From");
2158 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2159 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2163 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2166 rfc2047_encode_string (&env->subject);
2168 encode_headers (env->userhdrs);
2171 void mutt_unprepare_envelope (ENVELOPE * env)
2175 for (item = env->userhdrs; item; item = item->next)
2176 rfc2047_decode (&item->data);
2178 address_delete (&env->mail_followup_to);
2180 /* back conversions */
2181 rfc2047_decode_adrlist (env->to);
2182 rfc2047_decode_adrlist (env->cc);
2183 rfc2047_decode_adrlist (env->bcc);
2184 rfc2047_decode_adrlist (env->from);
2185 rfc2047_decode_adrlist (env->reply_to);
2186 rfc2047_decode (&env->subject);
2189 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2190 const char *resent_from, address_t * env_from)
2194 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2195 MESSAGE *msg = NULL;
2198 /* Try to bounce each message out, aborting if we get any failures. */
2199 for (i = 0; i < Context->msgcount; i++)
2200 if (Context->hdrs[i]->tagged)
2202 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2207 /* If we failed to open a message, return with error */
2208 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2214 mutt_mktemp (tempfile);
2215 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2216 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2218 if (!option (OPTBOUNCEDELIVERED))
2219 ch_flags |= CH_WEED_DELIVERED;
2221 fseeko (fp, h->offset, 0);
2222 fprintf (f, "Resent-From: %s", resent_from);
2223 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2224 if (MsgIdFormat && *MsgIdFormat)
2225 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2226 fputs ("Resent-To: ", f);
2227 mutt_write_address_list (to, f, 11, 0);
2228 mutt_copy_header (fp, h, f, ch_flags, NULL);
2230 mutt_copy_bytes (fp, f, h->content->length);
2233 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2234 h->content->encoding == ENC8BIT);
2238 mx_close_message (&msg);
2243 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2246 const char *fqdn = mutt_fqdn (1);
2247 char resent_from[STRING];
2251 resent_from[0] = '\0';
2252 from = mutt_default_from ();
2255 rfc822_qualify (from, fqdn);
2257 rfc2047_encode_adrlist (from, "Resent-From");
2258 if (mutt_addrlist_to_idna (from, &err)) {
2259 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2262 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2265 unset_option (OPTNEWSSEND);
2268 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2270 address_delete (&from);
2276 /* given a list of addresses, return a list of unique addresses */
2277 address_t *mutt_remove_duplicates (address_t * addr)
2279 address_t *top = addr;
2280 address_t **last = ⊤
2285 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2286 if (tmp->mailbox && addr->mailbox &&
2287 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2294 debug_print (2, ("Removing %s\n", addr->mailbox));
2299 address_delete (&addr);
2312 static void set_noconv_flags (BODY * b, short flag)
2314 for (; b; b = b->next) {
2315 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2316 set_noconv_flags (b->parts, flag);
2317 else if (b->type == TYPETEXT && b->noconv) {
2319 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2321 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2326 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2327 int post, char *fcc)
2331 char tempfile[_POSIX_PATH_MAX];
2332 FILE *tempfp = NULL;
2336 set_noconv_flags (hdr->content, 1);
2338 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2339 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2343 /* We need to add a Content-Length field to avoid problems where a line in
2344 * the message body begins with "From "
2346 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2347 mutt_mktemp (tempfile);
2348 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2349 mutt_perror (tempfile);
2350 mx_close_mailbox (&f, NULL);
2355 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2356 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2357 mx_close_mailbox (&f, NULL);
2361 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2362 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2364 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2367 /* (postponment) if this was a reply of some sort, <msgid> contians the
2368 * Message-ID: of message replied to. Save it using a special X-Mutt-
2369 * header so it can be picked up if the message is recalled at a later
2370 * point in time. This will allow the message to be marked as replied if
2371 * the same mailbox is still open.
2374 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2376 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2377 * it can be picked up when the message is recalled
2380 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2381 fprintf (msg->fp, "Status: RO\n");
2385 /* (postponment) if the mail is to be signed or encrypted, save this info */
2386 if (post && (hdr->security & APPLICATION_PGP)) {
2387 fputs ("X-Mutt-PGP: ", msg->fp);
2388 if (hdr->security & ENCRYPT)
2389 fputc ('E', msg->fp);
2390 if (hdr->security & SIGN) {
2391 fputc ('S', msg->fp);
2392 if (PgpSignAs && *PgpSignAs)
2393 fprintf (msg->fp, "<%s>", PgpSignAs);
2395 if (hdr->security & INLINE)
2396 fputc ('I', msg->fp);
2397 fputc ('\n', msg->fp);
2400 /* (postponment) if the mail is to be signed or encrypted, save this info */
2401 if (post && (hdr->security & APPLICATION_SMIME)) {
2402 fputs ("X-Mutt-SMIME: ", msg->fp);
2403 if (hdr->security & ENCRYPT) {
2404 fputc ('E', msg->fp);
2405 if (SmimeCryptAlg && *SmimeCryptAlg)
2406 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2408 if (hdr->security & SIGN) {
2409 fputc ('S', msg->fp);
2410 if (SmimeDefaultKey && *SmimeDefaultKey)
2411 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2413 if (hdr->security & INLINE)
2414 fputc ('I', msg->fp);
2415 fputc ('\n', msg->fp);
2419 /* (postponement) if the mail is to be sent through a mixmaster
2420 * chain, save that information
2423 if (post && hdr->chain && hdr->chain) {
2426 fputs ("X-Mutt-Mix:", msg->fp);
2427 for (p = hdr->chain; p; p = p->next)
2428 fprintf (msg->fp, " %s", (char *) p->data);
2430 fputc ('\n', msg->fp);
2435 char sasha[LONG_STRING];
2438 mutt_write_mime_body (hdr->content, tempfp);
2440 /* make sure the last line ends with a newline. Emacs doesn't ensure
2441 * this will happen, and it can cause problems parsing the mailbox
2444 fseeko (tempfp, -1, 2);
2445 if (fgetc (tempfp) != '\n') {
2446 fseeko (tempfp, 0, 2);
2447 fputc ('\n', tempfp);
2451 if (ferror (tempfp)) {
2452 debug_print (1, ("%s: write failed.\n", tempfile));
2455 mx_commit_message (msg, &f); /* XXX - really? */
2456 mx_close_message (&msg);
2457 mx_close_mailbox (&f, NULL);
2461 /* count the number of lines */
2463 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2465 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2466 fprintf (msg->fp, "Lines: %d\n\n", lines);
2468 /* copy the body and clean up */
2470 r = mutt_copy_stream (tempfp, msg->fp);
2471 if (fclose (tempfp) != 0)
2473 /* if there was an error, leave the temp version */
2478 fputc ('\n', msg->fp); /* finish off the header */
2479 r = mutt_write_mime_body (hdr->content, msg->fp);
2482 if (mx_commit_message (msg, &f) != 0)
2484 mx_close_message (&msg);
2485 mx_close_mailbox (&f, NULL);
2488 set_noconv_flags (hdr->content, 0);