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.
19 #include "recvattach.h"
20 #include "mutt_curses.h"
28 #include "mutt_crypt.h"
29 #include "mutt_idna.h"
34 #include "lib/debug.h"
45 #include <sys/utsname.h>
48 # include "mutt_libesmtp.h"
49 #endif /* USE_LIBESMTP */
55 #ifdef HAVE_SYSEXITS_H
57 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
61 /* If you are debugging this file, comment out the following line. */
70 extern char RFC822Specials[];
72 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
74 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
77 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
78 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
79 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
80 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
84 static char MsgIdPfx = 'A';
86 static void transform_to_7bit (BODY * a, FILE * fpin);
88 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
91 char line[77], savechar;
93 while ((c = fgetconv (fc)) != EOF) {
94 /* Wrap the line if needed. */
95 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
96 /* If the last character is "quoted", then be sure to move all three
97 * characters to the next line. Otherwise, just move the last
100 if (line[linelen - 3] == '=') {
101 line[linelen - 3] = 0;
106 line[1] = line[linelen - 2];
107 line[2] = line[linelen - 1];
111 savechar = line[linelen - 1];
112 line[linelen - 1] = '=';
121 /* Escape lines that begin with/only contain "the message separator". */
122 if (linelen == 4 && !str_ncmp ("From", line, 4)) {
123 strfcpy (line, "=46rom", sizeof (line));
126 else if (linelen == 4 && !str_ncmp ("from", line, 4)) {
127 strfcpy (line, "=66rom", sizeof (line));
130 else if (linelen == 1 && line[0] == '.') {
131 strfcpy (line, "=2E", sizeof (line));
136 if (c == '\n' && istext) {
137 /* Check to make sure there is no trailing space on this line. */
139 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
141 sprintf (line + linelen - 1, "=%2.2X",
142 (unsigned char) line[linelen - 1]);
146 int savechar = line[linelen - 1];
148 line[linelen - 1] = '=';
151 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
161 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
162 /* Check to make sure there is enough room for the quoted character.
163 * If not, wrap to the next line.
166 line[linelen++] = '=';
172 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
176 /* Don't worry about wrapping the line here. That will happen during
177 * the next iteration when I'll also know what the next character is.
183 /* Take care of anything left in the buffer */
185 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
186 /* take care of trailing whitespace */
188 sprintf (line + linelen - 1, "=%2.2X",
189 (unsigned char) line[linelen - 1]);
191 savechar = line[linelen - 1];
192 line[linelen - 1] = '=';
196 sprintf (line, "=%2.2X", (unsigned char) savechar);
205 static char b64_buffer[3];
206 static short b64_num;
207 static short b64_linelen;
209 static void b64_flush (FILE * fout)
216 if (b64_linelen >= 72) {
221 for (i = b64_num; i < 3; i++)
222 b64_buffer[i] = '\0';
224 fputc (B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
227 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
232 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
236 fputc (B64Chars[b64_buffer[2] & 0x3f], fout);
241 while (b64_linelen % 4) {
250 static void b64_putc (char c, FILE * fout)
255 b64_buffer[b64_num++] = c;
259 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
263 b64_num = b64_linelen = 0;
265 while ((ch = fgetconv (fc)) != EOF) {
266 if (istext && ch == '\n' && ch1 != '\r')
267 b64_putc ('\r', fout);
275 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
279 while ((ch = fgetconv (fc)) != EOF)
284 int mutt_write_mime_header (BODY * a, FILE * f)
294 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
297 len = 25 + str_len (a->subtype); /* approximate len. of content-type */
299 for (p = a->parameter; p; p = p->next) {
308 tmp = str_dup (p->value);
309 encode = rfc2231_encode_string (&tmp);
310 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
312 /* Dirty hack to make messages readable by Outlook Express
313 * for the Mac: force quotes around the boundary parameter
314 * even when they aren't needed.
317 if (!ascii_strcasecmp (p->attribute, "boundary")
318 && !strcmp (buffer, tmp))
319 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
323 tmplen = str_len (buffer) + str_len (p->attribute) + 1;
325 if (len + tmplen + 2 > 76) {
334 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
342 fprintf (f, "Content-Description: %s\n", a->description);
344 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
347 if (!(fn = a->d_filename))
353 /* Strip off the leading path... */
354 if ((t = strrchr (fn, '/')))
361 encode = rfc2231_encode_string (&tmp);
362 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
364 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
370 if (a->encoding != ENC7BIT)
371 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
373 /* Do NOT add the terminator here!!! */
374 return (ferror (f) ? -1 : 0);
377 # define write_as_text_part(a) (mutt_is_text_part(a) \
378 || ((WithCrypto & APPLICATION_PGP)\
379 && mutt_is_application_pgp(a)))
381 int mutt_write_mime_body (BODY * a, FILE * f)
383 char *p, boundary[SHORT_STRING];
384 char send_charset[SHORT_STRING];
389 if (a->type == TYPEMULTIPART) {
390 /* First, find the boundary to use */
391 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
392 debug_print (1, ("no boundary parameter found!\n"));
393 mutt_error _("No boundary parameter found! [report this error]");
397 strfcpy (boundary, p, sizeof (boundary));
399 for (t = a->parts; t; t = t->next) {
400 fprintf (f, "\n--%s\n", boundary);
401 if (mutt_write_mime_header (t, f) == -1)
404 if (mutt_write_mime_body (t, f) == -1)
407 fprintf (f, "\n--%s--\n", boundary);
408 return (ferror (f) ? -1 : 0);
411 /* This is pretty gross, but it's the best solution for now... */
412 if ((WithCrypto & APPLICATION_PGP)
413 && a->type == TYPEAPPLICATION
414 && str_cmp (a->subtype, "pgp-encrypted") == 0) {
415 fputs ("Version: 1\n", f);
419 if ((fpin = fopen (a->filename, "r")) == NULL) {
420 debug_print (1, ("%s no longer exists!\n", a->filename));
421 mutt_error (_("%s no longer exists!"), a->filename);
425 if (a->type == TYPETEXT && (!a->noconv))
426 fc = fgetconv_open (fpin, a->file_charset,
427 mutt_get_body_charset (send_charset,
428 sizeof (send_charset), a), 0);
430 fc = fgetconv_open (fpin, 0, 0, 0);
432 if (a->encoding == ENCQUOTEDPRINTABLE)
433 encode_quoted (fc, f, write_as_text_part (a));
434 else if (a->encoding == ENCBASE64)
435 encode_base64 (fc, f, write_as_text_part (a));
436 else if (a->type == TYPETEXT && (!a->noconv))
437 encode_8bit (fc, f, write_as_text_part (a));
439 mutt_copy_stream (fpin, f);
441 fgetconv_close (&fc);
444 return (ferror (f) ? -1 : 0);
447 #undef write_as_text_part
449 #define BOUNDARYLEN 16
450 void mutt_generate_boundary (PARAMETER ** parm)
452 char rs[BOUNDARYLEN + 1];
457 for (i = 0; i < BOUNDARYLEN; i++)
458 *p++ = B64Chars[LRAND () % sizeof (B64Chars)];
461 mutt_set_parameter ("boundary", rs, parm);
473 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
477 int whitespace = s->whitespace;
479 int linelen = s->linelen;
480 int was_cr = s->was_cr;
482 if (!d) { /* This signals EOF */
485 if (linelen > info->linemax)
486 info->linemax = linelen;
491 for (; dlen; d++, dlen--) {
504 if (linelen > info->linemax)
505 info->linemax = linelen;
520 if (linelen > info->linemax)
521 info->linemax = linelen;
526 else if (ch == '\r') {
534 else if (ch == '\t' || ch == '\f') {
538 else if (ch < 32 || ch == 127)
542 if ((ch == 'F') || (ch == 'f'))
552 if (linelen == 2 && ch != 'r')
554 else if (linelen == 3 && ch != 'o')
556 else if (linelen == 4) {
569 if (ch != ' ' && ch != '\t')
574 s->whitespace = whitespace;
576 s->linelen = linelen;
581 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
582 #define BUGGY_ICONV 1
585 * Find the best charset conversion of the file from fromcode into one
586 * of the tocodes. If successful, set *tocode and CONTENT *info and
587 * return the number of characters converted inexactly. If no
588 * conversion was possible, return -1.
590 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
591 * which would otherwise prevent us from knowing the number of inexact
592 * conversions. Where the candidate target charset is UTF-8 we avoid
593 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
594 * fails with some libraries.
596 * We assume that the output from iconv is never more than 4 times as
597 * long as the input for any pair of charsets we might be interested
600 static size_t convert_file_to (FILE * file, const char *fromcode,
601 int ncodes, const char **tocodes,
602 int *tocode, CONTENT * info)
606 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
607 ICONV_CONST char *ib, *ub;
609 size_t ibl, obl, ubl, ubl1, n, ret;
612 CONTENT_STATE *states;
615 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
616 if (cd1 == (iconv_t) (-1))
619 cd = mem_calloc (ncodes, sizeof (iconv_t));
620 score = mem_calloc (ncodes, sizeof (size_t));
621 states = mem_calloc (ncodes, sizeof (CONTENT_STATE));
622 infos = mem_calloc (ncodes, sizeof (CONTENT));
624 for (i = 0; i < ncodes; i++)
625 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
626 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
628 /* Special case for conversion to UTF-8 */
629 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
635 /* Try to fill input buffer */
636 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
639 /* Convert to UTF-8 */
641 ob = bufu, obl = sizeof (bufu);
642 n = iconv (cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
643 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
644 if (n == (size_t) (-1) &&
645 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
646 assert (errno == EILSEQ ||
647 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
653 /* Convert from UTF-8 */
654 for (i = 0; i < ncodes; i++)
655 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
656 ub = bufu, ubl = ubl1;
657 ob = bufo, obl = sizeof (bufo);
658 n = iconv (cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
659 if (n == (size_t) (-1)) {
660 assert (errno == E2BIG ||
661 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
662 score[i] = (size_t) (-1);
666 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
669 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
670 /* Special case for conversion to UTF-8 */
671 update_content_info (&infos[i], &states[i], bufu, ubl1);
674 /* Save unused input */
675 memmove (bufi, ib, ibl);
676 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
683 /* Find best score */
685 for (i = 0; i < ncodes; i++) {
686 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
687 /* Special case for conversion to UTF-8 */
692 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
694 else if (ret == (size_t) (-1) || score[i] < ret) {
701 if (ret != (size_t) (-1)) {
702 memcpy (info, &infos[*tocode], sizeof (CONTENT));
703 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
707 for (i = 0; i < ncodes; i++)
708 if (cd[i] != (iconv_t) (-1))
720 #endif /* !HAVE_ICONV */
724 * Find the first of the fromcodes that gives a valid conversion and
725 * the best charset conversion of the file into one of the tocodes. If
726 * successful, set *fromcode and *tocode to dynamically allocated
727 * strings, set CONTENT *info, and return the number of characters
728 * converted inexactly. If no conversion was possible, return -1.
730 * Both fromcodes and tocodes may be colon-separated lists of charsets.
731 * However, if fromcode is zero then fromcodes is assumed to be the
732 * name of a single charset even if it contains a colon.
734 static size_t convert_file_from_to (FILE * file,
735 const char *fromcodes,
736 const char *tocodes, char **fromcode,
737 char **tocode, CONTENT * info)
745 /* Count the tocodes */
747 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
748 if ((c1 = strchr (c, ':')) == c)
754 tcode = mem_malloc (ncodes * sizeof (char *));
755 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
756 if ((c1 = strchr (c, ':')) == c)
758 tcode[i] = str_substrdup (c, c1);
763 /* Try each fromcode in turn */
764 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
765 if ((c1 = strchr (c, ':')) == c)
767 fcode = str_substrdup (c, c1);
769 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
771 if (ret != (size_t) (-1)) {
781 /* There is only one fromcode */
782 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
784 if (ret != (size_t) (-1)) {
791 for (i = 0; i < ncodes; i++)
792 mem_free (&tcode[i]);
800 * Analyze the contents of a file to determine which MIME encoding to use.
801 * Also set the body charset, sometimes, or not.
803 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
808 char *fromcode = NULL;
819 if (stat (fname, &sb) == -1) {
820 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
824 if (!S_ISREG (sb.st_mode)) {
825 mutt_error (_("%s isn't a regular file."), fname);
829 if ((fp = fopen (fname, "r")) == NULL) {
830 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
834 info = mem_calloc (1, sizeof (CONTENT));
835 memset (&state, 0, sizeof (state));
837 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
838 char *chs = mutt_get_parameter ("charset", b->parameter);
839 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
840 FileCharset : Charset) : Charset;
841 if (Charset && (chs || SendCharset) &&
842 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
843 &fromcode, &tocode, info) != (size_t) (-1)) {
845 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
846 mutt_set_parameter ("charset", chsbuf, &b->parameter);
848 b->file_charset = fromcode;
856 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
857 update_content_info (info, &state, buffer, r);
858 update_content_info (info, &state, 0, 0);
862 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
863 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
865 && !mutt_is_us_ascii (Charset) ? Charset :
866 "unknown-8bit"), &b->parameter);
871 /* Given a file with path ``s'', see if there is a registered MIME type.
872 * returns the major MIME type, and copies the subtype to ``d''. First look
873 * for ~/.mime.types, then look in a system mime.types if we can find one.
874 * The longest match is used so that we can match `ps.gz' when `gz' also
878 int mutt_lookup_mime_type (BODY * att, const char *path)
882 char buf[LONG_STRING];
883 char subtype[STRING], xtype[STRING];
885 int szf, sze, cur_sze;
893 szf = str_len (path);
895 for (count = 0; count < 3; count++) {
897 * can't use strtok() because we use it in an inner loop below, so use
898 * a switch statement here instead.
902 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
905 strfcpy (buf, SYSCONFDIR "/muttng-mime.types", sizeof (buf));
908 strfcpy (buf, PKGDATADIR "/mime.types", sizeof (buf));
911 debug_print (1, ("Internal error, count = %d.\n", count));
912 goto bye; /* shouldn't happen */
915 if ((f = fopen (buf, "r")) != NULL) {
916 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
917 /* weed out any comments */
918 if ((p = strchr (buf, '#')))
921 /* remove any leading space. */
925 /* position on the next field in this line */
926 if ((p = strpbrk (ct, " \t")) == NULL)
931 /* cycle through the file extensions */
932 while ((p = strtok (p, " \t\n"))) {
934 if ((sze > cur_sze) && (szf >= sze) &&
935 (str_casecmp (path + szf - sze, p) == 0
936 || ascii_strcasecmp (path + szf - sze, p) == 0) && (szf == sze
943 /* get the content-type */
945 if ((p = strchr (ct, '/')) == NULL) {
946 /* malformed line, just skip it. */
951 for (q = p; *q && !ISSPACE (*q); q++);
953 str_substrcpy (subtype, p, q, sizeof (subtype));
955 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
956 strfcpy (xtype, ct, sizeof (xtype));
969 if (type != TYPEOTHER || *xtype != '\0') {
971 str_replace (&att->subtype, subtype);
972 str_replace (&att->xtype, xtype);
978 void mutt_message_to_7bit (BODY * a, FILE * fp)
980 char temp[_POSIX_PATH_MAX];
986 if (!a->filename && fp)
988 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
989 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
994 if (stat (a->filename, &sb) == -1) {
995 mutt_perror ("stat");
998 a->length = sb.st_size;
1002 if (!(fpout = safe_fopen (temp, "w+"))) {
1003 mutt_perror ("fopen");
1007 fseeko (fpin, a->offset, 0);
1008 a->parts = mutt_parse_messageRFC822 (fpin, a);
1010 transform_to_7bit (a->parts, fpin);
1012 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1013 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1015 fputs ("MIME-Version: 1.0\n", fpout);
1016 mutt_write_mime_header (a->parts, fpout);
1017 fputc ('\n', fpout);
1018 mutt_write_mime_body (a->parts, fpout);
1030 a->encoding = ENC7BIT;
1031 a->d_filename = a->filename;
1032 if (a->filename && a->unlink)
1033 unlink (a->filename);
1034 a->filename = str_dup (temp);
1036 if (stat (a->filename, &sb) == -1) {
1037 mutt_perror ("stat");
1040 a->length = sb.st_size;
1041 mutt_free_body (&a->parts);
1042 a->hdr->content = NULL;
1045 static void transform_to_7bit (BODY * a, FILE * fpin)
1047 char buff[_POSIX_PATH_MAX];
1051 memset (&s, 0, sizeof (s));
1052 for (; a; a = a->next) {
1053 if (a->type == TYPEMULTIPART) {
1054 if (a->encoding != ENC7BIT)
1055 a->encoding = ENC7BIT;
1057 transform_to_7bit (a->parts, fpin);
1059 else if (mutt_is_message_type (a->type, a->subtype)) {
1060 mutt_message_to_7bit (a, fpin);
1064 a->force_charset = 1;
1067 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1068 mutt_perror ("fopen");
1072 mutt_decode_attachment (a, &s);
1074 a->d_filename = a->filename;
1075 a->filename = str_dup (buff);
1077 if (stat (a->filename, &sb) == -1) {
1078 mutt_perror ("stat");
1081 a->length = sb.st_size;
1083 mutt_update_encoding (a);
1084 if (a->encoding == ENC8BIT)
1085 a->encoding = ENCQUOTEDPRINTABLE;
1086 else if (a->encoding == ENCBINARY)
1087 a->encoding = ENCBASE64;
1092 /* determine which Content-Transfer-Encoding to use */
1093 static void mutt_set_encoding (BODY * b, CONTENT * info)
1095 char send_charset[SHORT_STRING];
1097 if (b->type == TYPETEXT) {
1099 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1100 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1101 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1102 b->encoding = ENCQUOTEDPRINTABLE;
1103 else if (info->hibin)
1104 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1106 b->encoding = ENC7BIT;
1108 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1109 if (info->lobin || info->hibin) {
1110 if (option (OPTALLOW8BIT) && !info->lobin)
1111 b->encoding = ENC8BIT;
1113 mutt_message_to_7bit (b, NULL);
1116 b->encoding = ENC7BIT;
1118 else if (b->type == TYPEAPPLICATION
1119 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1120 b->encoding = ENC7BIT;
1123 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1124 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1127 /* Determine which encoding is smaller */
1128 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1129 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1130 b->encoding = ENCBASE64;
1132 b->encoding = ENCQUOTEDPRINTABLE;
1136 b->encoding = ENC7BIT;
1140 void mutt_stamp_attachment (BODY * a)
1142 a->stamp = time (NULL);
1145 /* Get a body's character set */
1147 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1151 if (b && b->type != TYPETEXT)
1155 p = mutt_get_parameter ("charset", b->parameter);
1158 mutt_canonical_charset (d, dlen, NONULL (p));
1160 strfcpy (d, "us-ascii", dlen);
1166 /* Assumes called from send mode where BODY->filename points to actual file */
1167 void mutt_update_encoding (BODY * a)
1170 char chsbuff[STRING];
1172 /* override noconv when it's us-ascii */
1173 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1176 if (!a->force_charset && !a->noconv)
1177 mutt_delete_parameter ("charset", &a->parameter);
1179 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1182 mutt_set_encoding (a, info);
1183 mutt_stamp_attachment (a);
1185 mem_free (&a->content);
1190 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1192 char buffer[LONG_STRING];
1195 int cmflags, chflags;
1196 int pgp = WithCrypto ? hdr->security : 0;
1199 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1200 (hdr->security & ENCRYPT)) {
1201 if (!crypt_valid_passphrase (hdr->security))
1206 mutt_mktemp (buffer);
1207 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1210 body = mutt_new_body ();
1211 body->type = TYPEMESSAGE;
1212 body->subtype = str_dup ("rfc822");
1213 body->filename = str_dup (buffer);
1216 body->disposition = DISPINLINE;
1219 mutt_parse_mime_message (ctx, hdr);
1224 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1225 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1226 chflags |= CH_MIME | CH_TXTPLAIN;
1227 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1228 if ((WithCrypto & APPLICATION_PGP))
1230 if ((WithCrypto & APPLICATION_SMIME))
1231 pgp &= ~SMIMEENCRYPT;
1233 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1234 if ((WithCrypto & APPLICATION_PGP)
1235 && mutt_is_multipart_encrypted (hdr->content)) {
1236 chflags |= CH_MIME | CH_NONEWLINE;
1237 cmflags = M_CM_DECODE_PGP;
1240 else if ((WithCrypto & APPLICATION_PGP)
1241 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1242 chflags |= CH_MIME | CH_TXTPLAIN;
1243 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1246 else if ((WithCrypto & APPLICATION_SMIME)
1247 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1248 chflags |= CH_MIME | CH_TXTPLAIN;
1249 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1250 pgp &= ~SMIMEENCRYPT;
1254 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1259 body->hdr = mutt_new_header ();
1260 body->hdr->offset = 0;
1261 /* we don't need the user headers here */
1262 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1264 body->hdr->security = pgp;
1265 mutt_update_encoding (body);
1266 body->parts = body->hdr->content;
1273 BODY *mutt_make_file_attach (const char *path)
1278 att = mutt_new_body ();
1279 att->filename = str_dup (path);
1281 /* Attempt to determine the appropriate content-type based on the filename
1288 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1289 path)) != TYPEOTHER || *xbuf != '\0') {
1291 att->subtype = str_dup (buf);
1292 att->xtype = str_dup (xbuf);
1297 mutt_lookup_mime_type (att, path);
1301 if ((info = mutt_get_content_info (path, att)) == NULL) {
1302 mutt_free_body (&att);
1306 if (!att->subtype) {
1307 if (info->lobin == 0
1308 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1310 * Statistically speaking, there should be more than 10% "lobin"
1311 * chars if this is really a binary file...
1313 att->type = TYPETEXT;
1314 att->subtype = str_dup ("plain");
1317 att->type = TYPEAPPLICATION;
1318 att->subtype = str_dup ("octet-stream");
1322 mutt_update_encoding (att);
1326 static int get_toplevel_encoding (BODY * a)
1330 for (; a; a = a->next) {
1331 if (a->encoding == ENCBINARY)
1333 else if (a->encoding == ENC8BIT)
1340 BODY *mutt_make_multipart (BODY * b)
1344 new = mutt_new_body ();
1345 new->type = TYPEMULTIPART;
1346 new->subtype = str_dup ("mixed");
1347 new->encoding = get_toplevel_encoding (b);
1348 mutt_generate_boundary (&new->parameter);
1350 new->disposition = DISPINLINE;
1356 /* remove the multipart body if it exists */
1357 BODY *mutt_remove_multipart (BODY * b)
1365 mutt_free_body (&t);
1370 char *mutt_make_date (char *s, size_t len)
1372 time_t t = time (NULL);
1373 struct tm *l = localtime (&t);
1374 time_t tz = mutt_local_tz (t);
1378 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1379 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1380 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1381 (int) tz / 60, (int) abs (tz) % 60);
1385 /* wrapper around mutt_write_address() so we can handle very large
1386 recipient lists without needing a huge temporary buffer in memory */
1387 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1391 char buf[LONG_STRING];
1399 rfc822_write_address (buf, sizeof (buf), adr, display);
1400 len = str_len (buf);
1401 if (count && linelen + len > 74) {
1403 linelen = len + 8; /* tab is usually about 8 spaces... */
1406 if (count && adr->mailbox) {
1414 if (!adr->group && adr->next && adr->next->mailbox) {
1424 /* arbitrary number of elements to grow the array by */
1429 /* need to write the list in reverse because they are stored in reverse order
1430 * when parsed to speed up threading
1432 void mutt_write_references (LIST * r, FILE * f)
1435 int refcnt = 0, refmax = 0;
1437 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1438 if (refcnt == refmax)
1439 mem_realloc (&ref, (refmax += REF_INC) * sizeof (LIST *));
1443 while (refcnt-- > 0) {
1445 fputs (ref[refcnt]->data, f);
1451 /* Note: all RFC2047 encoding should be done outside of this routine, except
1452 * for the "real name." This will allow this routine to be used more than
1453 * once, if necessary.
1455 * Likewise, all IDN processing should happen outside of this routine.
1457 * mode == 1 => "lite" mode (used for edit_hdrs)
1458 * mode == 0 => normal mode. write full header + MIME headers
1459 * mode == -1 => write just the envelope info (used for postponing messages)
1461 * privacy != 0 => will omit any headers which may identify the user.
1462 * Output generated is suitable for being sent through
1463 * anonymous remailer chains.
1467 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1468 int mode, int privacy)
1470 char buffer[LONG_STRING];
1472 LIST *tmp = env->userhdrs;
1473 int has_agent = 0; /* user defined user-agent header field exists */
1474 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1477 if (!option (OPTNEWSSEND))
1479 if (mode == 0 && !privacy)
1480 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1482 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1484 /* OPTUSEFROM is not consulted here so that we can still write a From:
1485 * field if the user sets it with the `my_hdr' command
1487 if (env->from && !privacy) {
1489 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1490 fprintf (fp, "From: %s\n", buffer);
1495 mutt_write_address_list (env->to, fp, 4, 0);
1499 if (!option (OPTNEWSSEND))
1501 if (EDIT_HEADER("To:"))
1502 fputs ("To: \n", fp);
1506 mutt_write_address_list (env->cc, fp, 4, 0);
1510 if (!option (OPTNEWSSEND))
1512 if (EDIT_HEADER("Cc:"))
1513 fputs ("Cc: \n", fp);
1516 if (mode != 0 || option (OPTWRITEBCC)) {
1517 fputs ("Bcc: ", fp);
1518 mutt_write_address_list (env->bcc, fp, 5, 0);
1523 if (!option (OPTNEWSSEND))
1525 if (EDIT_HEADER("Bcc:"))
1526 fputs ("Bcc: \n", fp);
1529 if (env->newsgroups)
1530 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1531 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1532 fputs ("Newsgroups: \n", fp);
1534 if (env->followup_to)
1535 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1536 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1537 fputs ("Followup-To: \n", fp);
1539 if (env->x_comment_to)
1540 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1541 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1542 EDIT_HEADER("X-Comment-To:"))
1543 fputs ("X-Comment-To: \n", fp);
1547 fprintf (fp, "Subject: %s\n", env->subject);
1548 else if (mode == 1 && EDIT_HEADER("Subject:"))
1549 fputs ("Subject: \n", fp);
1551 /* save message id if the user has set it */
1552 if (env->message_id && !privacy)
1553 fprintf (fp, "Message-ID: %s\n", env->message_id);
1555 if (env->reply_to) {
1556 fputs ("Reply-To: ", fp);
1557 mutt_write_address_list (env->reply_to, fp, 10, 0);
1559 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1560 fputs ("Reply-To: \n", fp);
1562 if (env->mail_followup_to)
1564 if (!option (OPTNEWSSEND))
1567 fputs ("Mail-Followup-To: ", fp);
1568 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1572 if (env->references) {
1573 fputs ("References:", fp);
1574 mutt_write_references (env->references, fp);
1578 /* Add the MIME headers */
1579 fputs ("MIME-Version: 1.0\n", fp);
1580 mutt_write_mime_header (attach, fp);
1583 if (env->in_reply_to) {
1584 fputs ("In-Reply-To:", fp);
1585 mutt_write_references (env->in_reply_to, fp);
1591 /* Add any user defined headers */
1592 for (; tmp; tmp = tmp->next) {
1593 if ((p = strchr (tmp->data, ':'))) {
1597 continue; /* don't emit empty fields. */
1599 /* check to see if the user has overridden the user-agent field */
1600 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1606 fputs (tmp->data, fp);
1611 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1615 if (OperatingSystem != NULL) {
1616 os = OperatingSystem;
1619 if (uname (&un) == -1) {
1626 /* Add a vanity header */
1627 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1630 list_del (&hdrs, (list_del_t*) _mem_free);
1632 return (ferror (fp) == 0 ? 0 : -1);
1635 static void encode_headers (LIST * h)
1641 for (; h; h = h->next) {
1642 if (!(p = strchr (h->data, ':')))
1653 rfc2047_encode_string (&tmp);
1654 mem_realloc (&h->data,
1655 str_len (h->data) + 2 + str_len (tmp) + 1);
1657 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1663 const char *mutt_fqdn (short may_hide_host)
1667 if (Fqdn && Fqdn[0] != '@') {
1670 if (may_hide_host && option (OPTHIDDENHOST)) {
1671 if ((p = strchr (Fqdn, '.')))
1674 /* sanity check: don't hide the host if
1675 * the fqdn is something like detebe.org.
1678 if (!p || !(q = strchr (p, '.')))
1686 static char mutt_normalized_char (char c)
1690 if (strchr (".!#$%&'*+-/=?^_`{|}~", c))
1692 return '.'; /* normalized character (we're stricter than RFC2822, 3.6.4) */
1695 static void mutt_gen_localpart (char *buf, unsigned int len, char *fmt)
1699 char tmp[SHORT_STRING];
1706 for (; *fmt; ++fmt) {
1712 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mday);
1713 str_ncat (buf, len, tmp, 2);
1716 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_hour);
1717 str_ncat (buf, len, tmp, 2);
1720 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mon + 1);
1721 str_ncat (buf, len, tmp, 2);
1724 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_min);
1725 str_ncat (buf, len, tmp, 2);
1728 snprintf (tmp, sizeof (tmp), "%lo", (unsigned long) now);
1729 str_ncat (buf, len, tmp, str_len (tmp));
1732 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) getpid ());
1733 str_ncat (buf, len, tmp, str_len (tmp));
1736 snprintf (tmp, sizeof (tmp), "%c", MsgIdPfx);
1737 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1738 str_ncat (buf, len, tmp, 1);
1741 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) rand ());
1742 str_ncat (buf, len, tmp, str_len (tmp));
1745 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) rand ());
1746 str_ncat (buf, len, tmp, str_len (tmp));
1749 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_sec);
1750 str_ncat (buf, len, tmp, 2);
1753 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) now);
1754 str_ncat (buf, len, tmp, str_len (tmp));
1757 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) now);
1758 str_ncat (buf, len, tmp, str_len (tmp));
1761 snprintf (tmp, sizeof (tmp), "%04d", tm->tm_year + 1900); /* this will break in the year 10000 ;-) */
1762 str_ncat (buf, len, tmp, 4);
1765 str_ncat (buf, len, "%", 1);
1768 str_ncat (buf, len, ".", 1); /* invalid formats are replaced by '.' */
1775 c = mutt_normalized_char (*fmt); /* @todo: filter out invalid characters */
1776 str_ncat (buf, len, &c, 1);
1781 char *mutt_gen_msgid (void)
1783 char buf[SHORT_STRING];
1784 char localpart[SHORT_STRING];
1785 unsigned int localpart_length;
1788 if (!(fqdn = mutt_fqdn (0)))
1789 fqdn = NONULL (Hostname);
1791 localpart_length = sizeof (buf) - str_len (fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1793 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1795 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1796 return (str_dup (buf));
1799 static RETSIGTYPE alarm_handler (int sig)
1804 /* invoke sendmail in a subshell
1805 path (in) path to program to execute
1806 args (in) arguments to pass to program
1807 msg (in) temp file containing message to send
1808 tempfile (out) if sendmail is put in the background, this points
1809 to the temporary file containing the stdout of the
1812 send_msg (const char *path, char **args, const char *msg, char **tempfile)
1818 mutt_block_signals_system ();
1821 /* we also don't want to be stopped right now */
1822 sigaddset (&set, SIGTSTP);
1823 sigprocmask (SIG_BLOCK, &set, NULL);
1825 if (SendmailWait >= 0) {
1826 char tmp[_POSIX_PATH_MAX];
1829 *tempfile = str_dup (tmp);
1832 if ((pid = fork ()) == 0) {
1833 struct sigaction act, oldalrm;
1835 /* save parent's ID before setsid() */
1838 /* we want the delivery to continue even after the main process dies,
1839 * so we put ourselves into another session right away
1843 /* next we close all open files */
1844 #if defined(OPEN_MAX)
1845 for (fd = 0; fd < OPEN_MAX; fd++)
1847 #elif defined(_POSIX_OPEN_MAX)
1848 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1856 /* now the second fork() */
1857 if ((pid = fork ()) == 0) {
1858 /* "msg" will be opened as stdin */
1859 if (open (msg, O_RDONLY, 0) < 0) {
1865 if (SendmailWait >= 0) {
1866 /* *tempfile will be opened as stdout */
1867 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1870 /* redirect stderr to *tempfile too */
1875 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1877 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1884 else if (pid == -1) {
1886 mem_free (tempfile);
1890 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1891 * SendmailWait = 0: wait forever
1892 * SendmailWait < 0: don't wait
1894 if (SendmailWait > 0) {
1896 act.sa_handler = alarm_handler;
1898 /* need to make sure waitpid() is interrupted on SIGALRM */
1899 act.sa_flags = SA_INTERRUPT;
1903 sigemptyset (&act.sa_mask);
1904 sigaction (SIGALRM, &act, &oldalrm);
1905 alarm (SendmailWait);
1907 else if (SendmailWait < 0)
1908 _exit (0xff & EX_OK);
1910 if (waitpid (pid, &st, 0) > 0) {
1911 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1912 if (SendmailWait && st == (0xff & EX_OK)) {
1913 unlink (*tempfile); /* no longer needed */
1914 mem_free (tempfile);
1918 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1919 if (SendmailWait > 0) {
1921 mem_free (tempfile);
1925 /* reset alarm; not really needed, but... */
1927 sigaction (SIGALRM, &oldalrm, NULL);
1929 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1930 /* the parent is already dead */
1932 mem_free (tempfile);
1938 sigprocmask (SIG_UNBLOCK, &set, NULL);
1940 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1941 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1943 st = S_ERR; /* error */
1945 mutt_unblock_signals_system (1);
1950 static char **add_args (char **args, size_t * argslen, size_t * argsmax,
1953 for (; addr; addr = addr->next) {
1954 /* weed out group mailboxes, since those are for display only */
1955 if (addr->mailbox && !addr->group) {
1956 if (*argslen == *argsmax)
1957 mem_realloc (&args, (*argsmax += 5) * sizeof (char *));
1958 args[(*argslen)++] = addr->mailbox;
1964 static char **add_option (char **args, size_t * argslen, size_t * argsmax,
1967 if (*argslen == *argsmax)
1968 mem_realloc (&args, (*argsmax += 5) * sizeof (char *));
1969 args[(*argslen)++] = s;
1973 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
1974 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
1975 const char *msg, /* file containing message */
1977 { /* message contains 8bit chars */
1978 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1980 size_t argslen = 0, argsmax = 0;
1984 if (option (OPTNEWSSEND)) {
1985 char cmd[LONG_STRING];
1987 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1990 i = nntp_post (msg);
1999 s = str_dup (Sendmail);
2003 while ((ps = strtok (ps, " "))) {
2004 if (argslen == argsmax)
2005 mem_realloc (&args, sizeof (char *) * (argsmax += 5));
2008 args[argslen++] = ps;
2010 path = str_dup (ps);
2011 ps = strrchr (ps, '/');
2016 args[argslen++] = ps;
2023 if (!option (OPTNEWSSEND)) {
2025 if (eightbit && option (OPTUSE8BITMIME))
2026 args = add_option (args, &argslen, &argsmax, "-B8BITMIME");
2028 if (option (OPTENVFROM)) {
2032 else if (from && !from->next)
2035 args = add_option (args, &argslen, &argsmax, "-f");
2036 args = add_args (args, &argslen, &argsmax, f);
2040 args = add_option (args, &argslen, &argsmax, "-N");
2041 args = add_option (args, &argslen, &argsmax, DsnNotify);
2044 args = add_option (args, &argslen, &argsmax, "-R");
2045 args = add_option (args, &argslen, &argsmax, DsnReturn);
2047 args = add_option (args, &argslen, &argsmax, "--");
2048 args = add_args (args, &argslen, &argsmax, to);
2049 args = add_args (args, &argslen, &argsmax, cc);
2050 args = add_args (args, &argslen, &argsmax, bcc);
2055 if (argslen == argsmax)
2056 mem_realloc (&args, sizeof (char *) * (++argsmax));
2058 args[argslen++] = NULL;
2060 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2062 const char *e = mutt_strsysexit (i);
2064 e = mutt_strsysexit (i);
2065 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2070 if (stat (childout, &st) == 0 && st.st_size > 0)
2071 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2079 mem_free (&childout);
2084 if (i == (EX_OK & 0xff))
2086 else if (i == S_BKG)
2093 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2094 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2095 const char *msg, /* file containing message */
2097 { /* message contains 8bit chars */
2100 if (!option (OPTNEWSSEND))
2103 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2106 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2109 /* appends string 'b' to string 'a', and returns the pointer to the new
2111 char *mutt_append_string (char *a, const char *b)
2113 size_t la = str_len (a);
2115 mem_realloc (&a, la + str_len (b) + 1);
2116 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2120 /* returns 1 if char `c' needs to be quoted to protect from shell
2121 interpretation when executing commands in a subshell */
2122 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2124 /* returns 1 if string `s' contains characters which could cause problems
2125 when used on a command line to execute a command */
2126 int mutt_needs_quote (const char *s)
2129 if (INVALID_CHAR (*s))
2136 /* Quote a string to prevent shell escapes when this string is used on the
2137 command line to send mail. */
2138 char *mutt_quote_string (const char *s)
2143 rlen = str_len (s) + 3;
2144 pr = r = (char *) mem_malloc (rlen);
2147 if (INVALID_CHAR (*s)) {
2150 mem_realloc (&r, ++rlen);
2161 /* For postponing (!final) do the necessary encodings only */
2162 void mutt_prepare_envelope (ENVELOPE * env, int final)
2164 char buffer[LONG_STRING];
2167 if (env->bcc && !(env->to || env->cc)) {
2168 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2169 * recipients if there is no To: or Cc: field, so attempt to suppress
2170 * it by using an empty To: field.
2172 env->to = rfc822_new_address ();
2174 env->to->next = rfc822_new_address ();
2177 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2180 env->to->mailbox = str_dup (buffer);
2183 mutt_set_followup_to (env);
2185 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2186 env->message_id = mutt_gen_msgid ();
2189 /* Take care of 8-bit => 7-bit conversion. */
2190 rfc2047_encode_adrlist (env->to, "To");
2191 rfc2047_encode_adrlist (env->cc, "Cc");
2192 rfc2047_encode_adrlist (env->bcc, "Bcc");
2193 rfc2047_encode_adrlist (env->from, "From");
2194 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2195 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2199 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2202 rfc2047_encode_string (&env->subject);
2204 encode_headers (env->userhdrs);
2207 void mutt_unprepare_envelope (ENVELOPE * env)
2211 for (item = env->userhdrs; item; item = item->next)
2212 rfc2047_decode (&item->data);
2214 rfc822_free_address (&env->mail_followup_to);
2216 /* back conversions */
2217 rfc2047_decode_adrlist (env->to);
2218 rfc2047_decode_adrlist (env->cc);
2219 rfc2047_decode_adrlist (env->bcc);
2220 rfc2047_decode_adrlist (env->from);
2221 rfc2047_decode_adrlist (env->reply_to);
2222 rfc2047_decode (&env->subject);
2225 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2226 const char *resent_from, ADDRESS * env_from)
2230 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2231 MESSAGE *msg = NULL;
2234 /* Try to bounce each message out, aborting if we get any failures. */
2235 for (i = 0; i < Context->msgcount; i++)
2236 if (Context->hdrs[i]->tagged)
2238 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2243 /* If we failed to open a message, return with error */
2244 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2250 mutt_mktemp (tempfile);
2251 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2252 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2254 if (!option (OPTBOUNCEDELIVERED))
2255 ch_flags |= CH_WEED_DELIVERED;
2257 fseeko (fp, h->offset, 0);
2258 fprintf (f, "Resent-From: %s", resent_from);
2259 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2260 if (MsgIdFormat && *MsgIdFormat)
2261 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2262 fputs ("Resent-To: ", f);
2263 mutt_write_address_list (to, f, 11, 0);
2264 mutt_copy_header (fp, h, f, ch_flags, NULL);
2266 mutt_copy_bytes (fp, f, h->content->length);
2269 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2270 h->content->encoding == ENC8BIT);
2274 mx_close_message (&msg);
2279 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2282 const char *fqdn = mutt_fqdn (1);
2283 char resent_from[STRING];
2287 resent_from[0] = '\0';
2288 from = mutt_default_from ();
2291 rfc822_qualify (from, fqdn);
2293 rfc2047_encode_adrlist (from, "Resent-From");
2294 if (mutt_addrlist_to_idna (from, &err)) {
2295 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2298 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2301 unset_option (OPTNEWSSEND);
2304 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2306 rfc822_free_address (&from);
2312 /* given a list of addresses, return a list of unique addresses */
2313 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2315 ADDRESS *top = addr;
2316 ADDRESS **last = ⊤
2321 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2322 if (tmp->mailbox && addr->mailbox &&
2323 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2330 debug_print (2, ("Removing %s\n", addr->mailbox));
2335 rfc822_free_address (&addr);
2348 static void set_noconv_flags (BODY * b, short flag)
2350 for (; b; b = b->next) {
2351 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2352 set_noconv_flags (b->parts, flag);
2353 else if (b->type == TYPETEXT && b->noconv) {
2355 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2357 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2362 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2363 int post, char *fcc)
2367 char tempfile[_POSIX_PATH_MAX];
2368 FILE *tempfp = NULL;
2372 set_noconv_flags (hdr->content, 1);
2374 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2375 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2379 /* We need to add a Content-Length field to avoid problems where a line in
2380 * the message body begins with "From "
2382 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2383 mutt_mktemp (tempfile);
2384 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2385 mutt_perror (tempfile);
2386 mx_close_mailbox (&f, NULL);
2391 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2392 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2393 mx_close_mailbox (&f, NULL);
2397 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2398 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2400 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2403 /* (postponment) if this was a reply of some sort, <msgid> contians the
2404 * Message-ID: of message replied to. Save it using a special X-Mutt-
2405 * header so it can be picked up if the message is recalled at a later
2406 * point in time. This will allow the message to be marked as replied if
2407 * the same mailbox is still open.
2410 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2412 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2413 * it can be picked up when the message is recalled
2416 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2417 fprintf (msg->fp, "Status: RO\n");
2421 /* (postponment) if the mail is to be signed or encrypted, save this info */
2422 if ((WithCrypto & APPLICATION_PGP)
2423 && post && (hdr->security & APPLICATION_PGP)) {
2424 fputs ("X-Mutt-PGP: ", msg->fp);
2425 if (hdr->security & ENCRYPT)
2426 fputc ('E', msg->fp);
2427 if (hdr->security & SIGN) {
2428 fputc ('S', msg->fp);
2429 if (PgpSignAs && *PgpSignAs)
2430 fprintf (msg->fp, "<%s>", PgpSignAs);
2432 if (hdr->security & INLINE)
2433 fputc ('I', msg->fp);
2434 fputc ('\n', msg->fp);
2437 /* (postponment) if the mail is to be signed or encrypted, save this info */
2438 if ((WithCrypto & APPLICATION_SMIME)
2439 && post && (hdr->security & APPLICATION_SMIME)) {
2440 fputs ("X-Mutt-SMIME: ", msg->fp);
2441 if (hdr->security & ENCRYPT) {
2442 fputc ('E', msg->fp);
2443 if (SmimeCryptAlg && *SmimeCryptAlg)
2444 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2446 if (hdr->security & SIGN) {
2447 fputc ('S', msg->fp);
2448 if (SmimeDefaultKey && *SmimeDefaultKey)
2449 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2451 if (hdr->security & INLINE)
2452 fputc ('I', msg->fp);
2453 fputc ('\n', msg->fp);
2457 /* (postponement) if the mail is to be sent through a mixmaster
2458 * chain, save that information
2461 if (post && hdr->chain && hdr->chain) {
2464 fputs ("X-Mutt-Mix:", msg->fp);
2465 for (p = hdr->chain; p; p = p->next)
2466 fprintf (msg->fp, " %s", (char *) p->data);
2468 fputc ('\n', msg->fp);
2473 char sasha[LONG_STRING];
2476 mutt_write_mime_body (hdr->content, tempfp);
2478 /* make sure the last line ends with a newline. Emacs doesn't ensure
2479 * this will happen, and it can cause problems parsing the mailbox
2482 fseeko (tempfp, -1, 2);
2483 if (fgetc (tempfp) != '\n') {
2484 fseeko (tempfp, 0, 2);
2485 fputc ('\n', tempfp);
2489 if (ferror (tempfp)) {
2490 debug_print (1, ("%s: write failed.\n", tempfile));
2493 mx_commit_message (msg, &f); /* XXX - really? */
2494 mx_close_message (&msg);
2495 mx_close_mailbox (&f, NULL);
2499 /* count the number of lines */
2501 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2503 fprintf (msg->fp, "Content-Length: " OFF_T_FMT "\n", ftello (tempfp));
2504 fprintf (msg->fp, "Lines: %d\n\n", lines);
2506 /* copy the body and clean up */
2508 r = mutt_copy_stream (tempfp, msg->fp);
2509 if (fclose (tempfp) != 0)
2511 /* if there was an error, leave the temp version */
2516 fputc ('\n', msg->fp); /* finish off the header */
2517 r = mutt_write_mime_body (hdr->content, msg->fp);
2520 if (mx_commit_message (msg, &f) != 0)
2522 mx_close_message (&msg);
2523 mx_close_mailbox (&f, NULL);
2526 set_noconv_flags (hdr->content, 0);