2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
5 * This file is part of mutt-ng, see http://www.muttng.org/.
6 * It's licensed under the GNU General Public License,
7 * please see the file GPL in the top level source directory.
16 #include <lib-lib/mem.h>
17 #include <lib-lib/ascii.h>
18 #include <lib-lib/str.h>
19 #include <lib-lib/macros.h>
23 #include "recvattach.h"
24 #include "mutt_curses.h"
32 #include "mutt_crypt.h"
33 #include "mutt_idna.h"
35 #include "lib/debug.h"
46 #include <sys/utsname.h>
49 # include "mutt_libesmtp.h"
50 #endif /* USE_LIBESMTP */
56 #ifdef HAVE_SYSEXITS_H
58 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
62 /* If you are debugging this file, comment out the following line. */
71 extern char RFC822Specials[];
73 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
75 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
78 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
79 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
80 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
81 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
85 static char MsgIdPfx = 'A';
87 static void transform_to_7bit (BODY * a, FILE * fpin);
89 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
92 char line[77], savechar;
94 while ((c = fgetconv (fc)) != EOF) {
95 /* Wrap the line if needed. */
96 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
97 /* If the last character is "quoted", then be sure to move all three
98 * characters to the next line. Otherwise, just move the last
101 if (line[linelen - 3] == '=') {
102 line[linelen - 3] = 0;
107 line[1] = line[linelen - 2];
108 line[2] = line[linelen - 1];
112 savechar = line[linelen - 1];
113 line[linelen - 1] = '=';
122 /* Escape lines that begin with/only contain "the message separator". */
123 if (linelen == 4 && !str_ncmp ("From", line, 4)) {
124 strfcpy (line, "=46rom", sizeof (line));
127 else if (linelen == 4 && !str_ncmp ("from", line, 4)) {
128 strfcpy (line, "=66rom", sizeof (line));
131 else if (linelen == 1 && line[0] == '.') {
132 strfcpy (line, "=2E", sizeof (line));
137 if (c == '\n' && istext) {
138 /* Check to make sure there is no trailing space on this line. */
140 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
142 sprintf (line + linelen - 1, "=%2.2X",
143 (unsigned char) line[linelen - 1]);
147 int savechar = line[linelen - 1];
149 line[linelen - 1] = '=';
152 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
162 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
163 /* Check to make sure there is enough room for the quoted character.
164 * If not, wrap to the next line.
167 line[linelen++] = '=';
173 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
177 /* Don't worry about wrapping the line here. That will happen during
178 * the next iteration when I'll also know what the next character is.
184 /* Take care of anything left in the buffer */
186 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
187 /* take care of trailing whitespace */
189 sprintf (line + linelen - 1, "=%2.2X",
190 (unsigned char) line[linelen - 1]);
192 savechar = line[linelen - 1];
193 line[linelen - 1] = '=';
197 sprintf (line, "=%2.2X", (unsigned char) savechar);
206 static char b64_buffer[3];
207 static short b64_num;
208 static short b64_linelen;
210 static void b64_flush (FILE * fout)
217 if (b64_linelen >= 72) {
222 for (i = b64_num; i < 3; i++)
223 b64_buffer[i] = '\0';
225 fputc (B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
228 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
233 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
237 fputc (B64Chars[b64_buffer[2] & 0x3f], fout);
242 while (b64_linelen % 4) {
251 static void b64_putc (char c, FILE * fout)
256 b64_buffer[b64_num++] = c;
260 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
264 b64_num = b64_linelen = 0;
266 while ((ch = fgetconv (fc)) != EOF) {
267 if (istext && ch == '\n' && ch1 != '\r')
268 b64_putc ('\r', fout);
276 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
280 while ((ch = fgetconv (fc)) != EOF)
285 int mutt_write_mime_header (BODY * a, FILE * f)
295 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
298 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
300 for (p = a->parameter; p; p = p->next) {
309 tmp = m_strdup(p->value);
310 encode = rfc2231_encode_string (&tmp);
311 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
313 /* Dirty hack to make messages readable by Outlook Express
314 * for the Mac: force quotes around the boundary parameter
315 * even when they aren't needed.
318 if (!ascii_strcasecmp (p->attribute, "boundary")
319 && !strcmp (buffer, tmp))
320 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
324 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
326 if (len + tmplen + 2 > 76) {
335 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
343 fprintf (f, "Content-Description: %s\n", a->description);
345 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
348 if (!(fn = a->d_filename))
354 /* Strip off the leading path... */
355 if ((t = strrchr (fn, '/')))
362 encode = rfc2231_encode_string (&tmp);
363 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
365 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
371 if (a->encoding != ENC7BIT)
372 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
374 /* Do NOT add the terminator here!!! */
375 return (ferror (f) ? -1 : 0);
378 # define write_as_text_part(a) (mutt_is_text_part(a) \
379 || ((WithCrypto & APPLICATION_PGP)\
380 && mutt_is_application_pgp(a)))
382 int mutt_write_mime_body (BODY * a, FILE * f)
384 char *p, boundary[SHORT_STRING];
385 char send_charset[SHORT_STRING];
390 if (a->type == TYPEMULTIPART) {
391 /* First, find the boundary to use */
392 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
393 debug_print (1, ("no boundary parameter found!\n"));
394 mutt_error _("No boundary parameter found! [report this error]");
398 strfcpy (boundary, p, sizeof (boundary));
400 for (t = a->parts; t; t = t->next) {
401 fprintf (f, "\n--%s\n", boundary);
402 if (mutt_write_mime_header (t, f) == -1)
405 if (mutt_write_mime_body (t, f) == -1)
408 fprintf (f, "\n--%s--\n", boundary);
409 return (ferror (f) ? -1 : 0);
412 /* This is pretty gross, but it's the best solution for now... */
413 if ((WithCrypto & APPLICATION_PGP)
414 && a->type == TYPEAPPLICATION
415 && str_cmp (a->subtype, "pgp-encrypted") == 0) {
416 fputs ("Version: 1\n", f);
420 if ((fpin = fopen (a->filename, "r")) == NULL) {
421 debug_print (1, ("%s no longer exists!\n", a->filename));
422 mutt_error (_("%s no longer exists!"), a->filename);
426 if (a->type == TYPETEXT && (!a->noconv))
427 fc = fgetconv_open (fpin, a->file_charset,
428 mutt_get_body_charset (send_charset,
429 sizeof (send_charset), a), 0);
431 fc = fgetconv_open (fpin, 0, 0, 0);
433 if (a->encoding == ENCQUOTEDPRINTABLE)
434 encode_quoted (fc, f, write_as_text_part (a));
435 else if (a->encoding == ENCBASE64)
436 encode_base64 (fc, f, write_as_text_part (a));
437 else if (a->type == TYPETEXT && (!a->noconv))
438 encode_8bit (fc, f, write_as_text_part (a));
440 mutt_copy_stream (fpin, f);
442 fgetconv_close (&fc);
445 return (ferror (f) ? -1 : 0);
448 #undef write_as_text_part
450 #define BOUNDARYLEN 16
451 void mutt_generate_boundary (PARAMETER ** parm)
453 char rs[BOUNDARYLEN + 1];
458 for (i = 0; i < BOUNDARYLEN; i++)
459 *p++ = B64Chars[LRAND () % sizeof (B64Chars)];
462 mutt_set_parameter ("boundary", rs, parm);
474 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
478 int whitespace = s->whitespace;
480 int linelen = s->linelen;
481 int was_cr = s->was_cr;
483 if (!d) { /* This signals EOF */
486 if (linelen > info->linemax)
487 info->linemax = linelen;
492 for (; dlen; d++, dlen--) {
505 if (linelen > info->linemax)
506 info->linemax = linelen;
521 if (linelen > info->linemax)
522 info->linemax = linelen;
527 else if (ch == '\r') {
535 else if (ch == '\t' || ch == '\f') {
539 else if (ch < 32 || ch == 127)
543 if ((ch == 'F') || (ch == 'f'))
553 if (linelen == 2 && ch != 'r')
555 else if (linelen == 3 && ch != 'o')
557 else if (linelen == 4) {
570 if (ch != ' ' && ch != '\t')
575 s->whitespace = whitespace;
577 s->linelen = linelen;
582 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
583 #define BUGGY_ICONV 1
586 * Find the best charset conversion of the file from fromcode into one
587 * of the tocodes. If successful, set *tocode and CONTENT *info and
588 * return the number of characters converted inexactly. If no
589 * conversion was possible, return -1.
591 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
592 * which would otherwise prevent us from knowing the number of inexact
593 * conversions. Where the candidate target charset is UTF-8 we avoid
594 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
595 * fails with some libraries.
597 * We assume that the output from iconv is never more than 4 times as
598 * long as the input for any pair of charsets we might be interested
601 static size_t convert_file_to (FILE * file, const char *fromcode,
602 int ncodes, const char **tocodes,
603 int *tocode, CONTENT * info)
607 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
610 size_t ibl, obl, ubl, ubl1, n, ret;
613 CONTENT_STATE *states;
616 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
617 if (cd1 == (iconv_t) (-1))
620 cd = p_new(iconv_t, ncodes);
621 score = p_new(size_t, ncodes);
622 states = p_new(CONTENT_STATE, ncodes);
623 infos = p_new(CONTENT, ncodes);
625 for (i = 0; i < ncodes; i++)
626 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
627 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
629 /* Special case for conversion to UTF-8 */
630 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
636 /* Try to fill input buffer */
637 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
640 /* Convert to UTF-8 */
642 ob = bufu, obl = sizeof (bufu);
643 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
644 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
645 if (n == (size_t) (-1) &&
646 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
647 assert (errno == EILSEQ ||
648 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
654 /* Convert from UTF-8 */
655 for (i = 0; i < ncodes; i++)
656 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
657 ub = bufu, ubl = ubl1;
658 ob = bufo, obl = sizeof (bufo);
659 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
660 if (n == (size_t) (-1)) {
661 assert (errno == E2BIG ||
662 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
663 score[i] = (size_t) (-1);
667 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
670 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
671 /* Special case for conversion to UTF-8 */
672 update_content_info (&infos[i], &states[i], bufu, ubl1);
675 /* Save unused input */
676 memmove (bufi, ib, ibl);
677 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
684 /* Find best score */
686 for (i = 0; i < ncodes; i++) {
687 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
688 /* Special case for conversion to UTF-8 */
693 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
695 else if (ret == (size_t) (-1) || score[i] < ret) {
702 if (ret != (size_t) (-1)) {
703 memcpy (info, &infos[*tocode], sizeof (CONTENT));
704 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
708 for (i = 0; i < ncodes; i++)
709 if (cd[i] != (iconv_t) (-1))
721 #endif /* !HAVE_ICONV */
725 * Find the first of the fromcodes that gives a valid conversion and
726 * the best charset conversion of the file into one of the tocodes. If
727 * successful, set *fromcode and *tocode to dynamically allocated
728 * strings, set CONTENT *info, and return the number of characters
729 * converted inexactly. If no conversion was possible, return -1.
731 * Both fromcodes and tocodes may be colon-separated lists of charsets.
732 * However, if fromcode is zero then fromcodes is assumed to be the
733 * name of a single charset even if it contains a colon.
735 static size_t convert_file_from_to (FILE * file,
736 const char *fromcodes,
737 const char *tocodes, char **fromcode,
738 char **tocode, CONTENT * info)
746 /* Count the tocodes */
748 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
749 if ((c1 = strchr (c, ':')) == c)
755 tcode = p_new(char *, ncodes);
756 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
757 if ((c1 = strchr (c, ':')) == c)
759 tcode[i] = str_substrdup (c, c1);
764 /* Try each fromcode in turn */
765 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
766 if ((c1 = strchr (c, ':')) == c)
768 fcode = str_substrdup (c, c1);
770 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
772 if (ret != (size_t) (-1)) {
782 /* There is only one fromcode */
783 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
785 if (ret != (size_t) (-1)) {
792 for (i = 0; i < ncodes; i++)
801 * Analyze the contents of a file to determine which MIME encoding to use.
802 * Also set the body charset, sometimes, or not.
804 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
809 char *fromcode = NULL;
820 if (stat (fname, &sb) == -1) {
821 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
825 if (!S_ISREG (sb.st_mode)) {
826 mutt_error (_("%s isn't a regular file."), fname);
830 if ((fp = fopen (fname, "r")) == NULL) {
831 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
835 info = p_new(CONTENT, 1);
838 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
839 char *chs = mutt_get_parameter ("charset", b->parameter);
840 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
841 FileCharset : Charset) : Charset;
842 if (Charset && (chs || SendCharset) &&
843 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
844 &fromcode, &tocode, info) != (size_t) (-1)) {
846 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
847 mutt_set_parameter ("charset", chsbuf, &b->parameter);
849 b->file_charset = fromcode;
857 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
858 update_content_info (info, &state, buffer, r);
859 update_content_info (info, &state, 0, 0);
863 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
864 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
866 && !mutt_is_us_ascii (Charset) ? Charset :
867 "unknown-8bit"), &b->parameter);
872 /* Given a file with path ``s'', see if there is a registered MIME type.
873 * returns the major MIME type, and copies the subtype to ``d''. First look
874 * for ~/.mime.types, then look in a system mime.types if we can find one.
875 * The longest match is used so that we can match `ps.gz' when `gz' also
879 int mutt_lookup_mime_type (BODY * att, const char *path)
883 char buf[LONG_STRING];
884 char subtype[STRING], xtype[STRING];
886 int szf, sze, cur_sze;
894 szf = m_strlen(path);
896 for (count = 0; count < 4; count++) {
898 * can't use strtok() because we use it in an inner loop below, so use
899 * a switch statement here instead.
903 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
906 strfcpy (buf, SYSCONFDIR "/muttng-mime.types", sizeof (buf));
909 strfcpy (buf, PKGDATADIR "/mime.types", sizeof (buf));
912 strfcpy (buf, SYSCONFDIR "/mime.types", sizeof (buf));
915 debug_print (1, ("Internal error, count = %d.\n", count));
916 goto bye; /* shouldn't happen */
919 if ((f = fopen (buf, "r")) != NULL) {
920 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
921 /* weed out any comments */
922 if ((p = strchr (buf, '#')))
925 /* remove any leading space. */
929 /* position on the next field in this line */
930 if ((p = strpbrk (ct, " \t")) == NULL)
935 /* cycle through the file extensions */
936 while ((p = strtok (p, " \t\n"))) {
938 if ((sze > cur_sze) && (szf >= sze) &&
939 (str_casecmp (path + szf - sze, p) == 0
940 || ascii_strcasecmp (path + szf - sze, p) == 0) && (szf == sze
947 /* get the content-type */
949 if ((p = strchr (ct, '/')) == NULL) {
950 /* malformed line, just skip it. */
955 for (q = p; *q && !ISSPACE (*q); q++);
957 str_substrcpy (subtype, p, q, sizeof (subtype));
959 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
960 strfcpy (xtype, ct, sizeof (xtype));
973 if (type != TYPEOTHER || *xtype != '\0') {
975 str_replace (&att->subtype, subtype);
976 str_replace (&att->xtype, xtype);
982 void mutt_message_to_7bit (BODY * a, FILE * fp)
984 char temp[_POSIX_PATH_MAX];
990 if (!a->filename && fp)
992 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
993 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
998 if (stat (a->filename, &sb) == -1) {
999 mutt_perror ("stat");
1002 a->length = sb.st_size;
1006 if (!(fpout = safe_fopen (temp, "w+"))) {
1007 mutt_perror ("fopen");
1011 fseeko (fpin, a->offset, 0);
1012 a->parts = mutt_parse_messageRFC822 (fpin, a);
1014 transform_to_7bit (a->parts, fpin);
1016 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1017 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1019 fputs ("MIME-Version: 1.0\n", fpout);
1020 mutt_write_mime_header (a->parts, fpout);
1021 fputc ('\n', fpout);
1022 mutt_write_mime_body (a->parts, fpout);
1034 a->encoding = ENC7BIT;
1035 a->d_filename = a->filename;
1036 if (a->filename && a->unlink)
1037 unlink (a->filename);
1038 a->filename = m_strdup(temp);
1040 if (stat (a->filename, &sb) == -1) {
1041 mutt_perror ("stat");
1044 a->length = sb.st_size;
1045 mutt_free_body (&a->parts);
1046 a->hdr->content = NULL;
1049 static void transform_to_7bit (BODY * a, FILE * fpin)
1051 char buff[_POSIX_PATH_MAX];
1056 for (; a; a = a->next) {
1057 if (a->type == TYPEMULTIPART) {
1058 if (a->encoding != ENC7BIT)
1059 a->encoding = ENC7BIT;
1061 transform_to_7bit (a->parts, fpin);
1063 else if (mutt_is_message_type (a->type, a->subtype)) {
1064 mutt_message_to_7bit (a, fpin);
1068 a->force_charset = 1;
1071 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1072 mutt_perror ("fopen");
1076 mutt_decode_attachment (a, &s);
1078 a->d_filename = a->filename;
1079 a->filename = m_strdup(buff);
1081 if (stat (a->filename, &sb) == -1) {
1082 mutt_perror ("stat");
1085 a->length = sb.st_size;
1087 mutt_update_encoding (a);
1088 if (a->encoding == ENC8BIT)
1089 a->encoding = ENCQUOTEDPRINTABLE;
1090 else if (a->encoding == ENCBINARY)
1091 a->encoding = ENCBASE64;
1096 /* determine which Content-Transfer-Encoding to use */
1097 static void mutt_set_encoding (BODY * b, CONTENT * info)
1099 char send_charset[SHORT_STRING];
1101 if (b->type == TYPETEXT) {
1103 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1104 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1105 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1106 b->encoding = ENCQUOTEDPRINTABLE;
1107 else if (info->hibin)
1108 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1110 b->encoding = ENC7BIT;
1112 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1113 if (info->lobin || info->hibin) {
1114 if (option (OPTALLOW8BIT) && !info->lobin)
1115 b->encoding = ENC8BIT;
1117 mutt_message_to_7bit (b, NULL);
1120 b->encoding = ENC7BIT;
1122 else if (b->type == TYPEAPPLICATION
1123 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1124 b->encoding = ENC7BIT;
1127 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1128 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1131 /* Determine which encoding is smaller */
1132 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1133 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1134 b->encoding = ENCBASE64;
1136 b->encoding = ENCQUOTEDPRINTABLE;
1140 b->encoding = ENC7BIT;
1144 void mutt_stamp_attachment (BODY * a)
1146 a->stamp = time (NULL);
1149 /* Get a body's character set */
1151 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1155 if (b && b->type != TYPETEXT)
1159 p = mutt_get_parameter ("charset", b->parameter);
1162 mutt_canonical_charset (d, dlen, NONULL (p));
1164 strfcpy (d, "us-ascii", dlen);
1170 /* Assumes called from send mode where BODY->filename points to actual file */
1171 void mutt_update_encoding (BODY * a)
1174 char chsbuff[STRING];
1176 /* override noconv when it's us-ascii */
1177 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1180 if (!a->force_charset && !a->noconv)
1181 mutt_delete_parameter ("charset", &a->parameter);
1183 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1186 mutt_set_encoding (a, info);
1187 mutt_stamp_attachment (a);
1189 p_delete(&a->content);
1194 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1196 char buffer[LONG_STRING];
1199 int cmflags, chflags;
1200 int pgp = WithCrypto ? hdr->security : 0;
1203 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1204 (hdr->security & ENCRYPT)) {
1205 if (!crypt_valid_passphrase (hdr->security))
1210 mutt_mktemp (buffer);
1211 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1214 body = mutt_new_body ();
1215 body->type = TYPEMESSAGE;
1216 body->subtype = m_strdup("rfc822");
1217 body->filename = m_strdup(buffer);
1220 body->disposition = DISPINLINE;
1223 mutt_parse_mime_message (ctx, hdr);
1228 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1229 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1230 chflags |= CH_MIME | CH_TXTPLAIN;
1231 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1232 if ((WithCrypto & APPLICATION_PGP))
1234 if ((WithCrypto & APPLICATION_SMIME))
1235 pgp &= ~SMIMEENCRYPT;
1237 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1238 if ((WithCrypto & APPLICATION_PGP)
1239 && mutt_is_multipart_encrypted (hdr->content)) {
1240 chflags |= CH_MIME | CH_NONEWLINE;
1241 cmflags = M_CM_DECODE_PGP;
1244 else if ((WithCrypto & APPLICATION_PGP)
1245 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1246 chflags |= CH_MIME | CH_TXTPLAIN;
1247 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1250 else if ((WithCrypto & APPLICATION_SMIME)
1251 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1252 chflags |= CH_MIME | CH_TXTPLAIN;
1253 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1254 pgp &= ~SMIMEENCRYPT;
1258 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1263 body->hdr = mutt_new_header ();
1264 body->hdr->offset = 0;
1265 /* we don't need the user headers here */
1266 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1268 body->hdr->security = pgp;
1269 mutt_update_encoding (body);
1270 body->parts = body->hdr->content;
1277 BODY *mutt_make_file_attach (const char *path)
1282 att = mutt_new_body ();
1283 att->filename = m_strdup(path);
1285 /* Attempt to determine the appropriate content-type based on the filename
1292 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1293 path)) != TYPEOTHER || *xbuf != '\0') {
1295 att->subtype = m_strdup(buf);
1296 att->xtype = m_strdup(xbuf);
1301 mutt_lookup_mime_type (att, path);
1305 if ((info = mutt_get_content_info (path, att)) == NULL) {
1306 mutt_free_body (&att);
1310 if (!att->subtype) {
1311 if (info->lobin == 0
1312 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1314 * Statistically speaking, there should be more than 10% "lobin"
1315 * chars if this is really a binary file...
1317 att->type = TYPETEXT;
1318 att->subtype = m_strdup("plain");
1321 att->type = TYPEAPPLICATION;
1322 att->subtype = m_strdup("octet-stream");
1326 mutt_update_encoding (att);
1330 static int get_toplevel_encoding (BODY * a)
1334 for (; a; a = a->next) {
1335 if (a->encoding == ENCBINARY)
1337 else if (a->encoding == ENC8BIT)
1344 BODY *mutt_make_multipart (BODY * b)
1348 new = mutt_new_body ();
1349 new->type = TYPEMULTIPART;
1350 new->subtype = m_strdup("mixed");
1351 new->encoding = get_toplevel_encoding (b);
1352 mutt_generate_boundary (&new->parameter);
1354 new->disposition = DISPINLINE;
1360 /* remove the multipart body if it exists */
1361 BODY *mutt_remove_multipart (BODY * b)
1369 mutt_free_body (&t);
1374 char *mutt_make_date (char *s, size_t len)
1376 time_t t = time (NULL);
1377 struct tm *l = localtime (&t);
1378 time_t tz = mutt_local_tz (t);
1382 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1383 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1384 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1385 (int) tz / 60, (int) abs (tz) % 60);
1389 /* wrapper around mutt_write_address() so we can handle very large
1390 recipient lists without needing a huge temporary buffer in memory */
1391 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1395 char buf[LONG_STRING];
1403 rfc822_write_address (buf, sizeof (buf), adr, display);
1404 len = m_strlen(buf);
1405 if (count && linelen + len > 74) {
1407 linelen = len + 8; /* tab is usually about 8 spaces... */
1410 if (count && adr->mailbox) {
1418 if (!adr->group && adr->next && adr->next->mailbox) {
1428 /* arbitrary number of elements to grow the array by */
1433 /* need to write the list in reverse because they are stored in reverse order
1434 * when parsed to speed up threading
1436 void mutt_write_references (LIST * r, FILE * f)
1439 int refcnt = 0, refmax = 0;
1441 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1442 if (refcnt == refmax)
1443 p_realloc(&ref, refmax += REF_INC);
1447 while (refcnt-- > 0) {
1449 fputs (ref[refcnt]->data, f);
1455 /* Note: all RFC2047 encoding should be done outside of this routine, except
1456 * for the "real name." This will allow this routine to be used more than
1457 * once, if necessary.
1459 * Likewise, all IDN processing should happen outside of this routine.
1461 * mode == 1 => "lite" mode (used for edit_hdrs)
1462 * mode == 0 => normal mode. write full header + MIME headers
1463 * mode == -1 => write just the envelope info (used for postponing messages)
1465 * privacy != 0 => will omit any headers which may identify the user.
1466 * Output generated is suitable for being sent through
1467 * anonymous remailer chains.
1471 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1472 int mode, int privacy)
1474 char buffer[LONG_STRING];
1476 LIST *tmp = env->userhdrs;
1477 int has_agent = 0; /* user defined user-agent header field exists */
1478 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1481 if (!option (OPTNEWSSEND))
1483 if (mode == 0 && !privacy)
1484 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1486 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1488 /* OPTUSEFROM is not consulted here so that we can still write a From:
1489 * field if the user sets it with the `my_hdr' command
1491 if (env->from && !privacy) {
1493 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1494 fprintf (fp, "From: %s\n", buffer);
1499 mutt_write_address_list (env->to, fp, 4, 0);
1503 if (!option (OPTNEWSSEND))
1505 if (EDIT_HEADER("To:"))
1506 fputs ("To:\n", fp);
1510 mutt_write_address_list (env->cc, fp, 4, 0);
1514 if (!option (OPTNEWSSEND))
1516 if (EDIT_HEADER("Cc:"))
1517 fputs ("Cc:\n", fp);
1520 if (mode != 0 || option (OPTWRITEBCC)) {
1521 fputs ("Bcc: ", fp);
1522 mutt_write_address_list (env->bcc, fp, 5, 0);
1527 if (!option (OPTNEWSSEND))
1529 if (EDIT_HEADER("Bcc:"))
1530 fputs ("Bcc:\n", fp);
1533 if (env->newsgroups)
1534 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1535 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1536 fputs ("Newsgroups:\n", fp);
1538 if (env->followup_to)
1539 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1540 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1541 fputs ("Followup-To:\n", fp);
1543 if (env->x_comment_to)
1544 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1545 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1546 EDIT_HEADER("X-Comment-To:"))
1547 fputs ("X-Comment-To:\n", fp);
1551 fprintf (fp, "Subject: %s\n", env->subject);
1552 else if (mode == 1 && EDIT_HEADER("Subject:"))
1553 fputs ("Subject:\n", fp);
1555 /* save message id if the user has set it */
1556 if (env->message_id && !privacy)
1557 fprintf (fp, "Message-ID: %s\n", env->message_id);
1559 if (env->reply_to) {
1560 fputs ("Reply-To: ", fp);
1561 mutt_write_address_list (env->reply_to, fp, 10, 0);
1563 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1564 fputs ("Reply-To:\n", fp);
1566 if (env->mail_followup_to)
1568 if (!option (OPTNEWSSEND))
1571 fputs ("Mail-Followup-To: ", fp);
1572 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1576 if (env->references) {
1577 fputs ("References:", fp);
1578 mutt_write_references (env->references, fp);
1582 /* Add the MIME headers */
1583 fputs ("MIME-Version: 1.0\n", fp);
1584 mutt_write_mime_header (attach, fp);
1587 if (env->in_reply_to) {
1588 fputs ("In-Reply-To:", fp);
1589 mutt_write_references (env->in_reply_to, fp);
1595 /* Add any user defined headers */
1596 for (; tmp; tmp = tmp->next) {
1597 if ((p = strchr (tmp->data, ':'))) {
1601 continue; /* don't emit empty fields. */
1603 /* check to see if the user has overridden the user-agent field */
1604 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1610 fputs (tmp->data, fp);
1615 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1618 if (OperatingSystem != NULL) {
1619 os = OperatingSystem;
1622 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1624 /* Add a vanity header */
1625 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1628 list_del (&hdrs, (list_del_t*)xmemfree);
1630 return (ferror (fp) == 0 ? 0 : -1);
1633 static void encode_headers (LIST * h)
1639 for (; h; h = h->next) {
1640 if (!(p = strchr (h->data, ':')))
1651 rfc2047_encode_string (&tmp);
1652 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1654 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1660 const char *mutt_fqdn (short may_hide_host)
1664 if (Fqdn && Fqdn[0] != '@') {
1667 if (may_hide_host && option (OPTHIDDENHOST)) {
1668 if ((p = strchr (Fqdn, '.')))
1671 /* sanity check: don't hide the host if
1672 * the fqdn is something like detebe.org.
1675 if (!p || !(q = strchr (p, '.')))
1683 static char mutt_normalized_char (char c)
1687 if (strchr (".!#$%&'*+-/=?^_`{|}~", c))
1689 return '.'; /* normalized character (we're stricter than RFC2822, 3.6.4) */
1692 static void mutt_gen_localpart (char *buf, unsigned int len, char *fmt)
1696 char tmp[SHORT_STRING];
1703 for (; *fmt; ++fmt) {
1709 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mday);
1710 str_ncat (buf, len, tmp, 2);
1713 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_hour);
1714 str_ncat (buf, len, tmp, 2);
1717 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mon + 1);
1718 str_ncat (buf, len, tmp, 2);
1721 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_min);
1722 str_ncat (buf, len, tmp, 2);
1725 snprintf (tmp, sizeof (tmp), "%lo", (unsigned long) now);
1726 str_ncat (buf, len, tmp, m_strlen(tmp));
1729 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) getpid ());
1730 str_ncat (buf, len, tmp, m_strlen(tmp));
1733 snprintf (tmp, sizeof (tmp), "%c", MsgIdPfx);
1734 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1735 str_ncat (buf, len, tmp, 1);
1738 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) rand ());
1739 str_ncat (buf, len, tmp, m_strlen(tmp));
1742 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) rand ());
1743 str_ncat (buf, len, tmp, m_strlen(tmp));
1746 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_sec);
1747 str_ncat (buf, len, tmp, 2);
1750 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) now);
1751 str_ncat (buf, len, tmp, m_strlen(tmp));
1754 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) now);
1755 str_ncat (buf, len, tmp, m_strlen(tmp));
1758 snprintf (tmp, sizeof (tmp), "%04d", tm->tm_year + 1900); /* this will break in the year 10000 ;-) */
1759 str_ncat (buf, len, tmp, 4);
1762 str_ncat (buf, len, "%", 1);
1765 str_ncat (buf, len, ".", 1); /* invalid formats are replaced by '.' */
1772 c = mutt_normalized_char (*fmt); /* @todo: filter out invalid characters */
1773 str_ncat (buf, len, &c, 1);
1778 char *mutt_gen_msgid (void)
1780 char buf[SHORT_STRING];
1781 char localpart[SHORT_STRING];
1782 unsigned int localpart_length;
1785 if (!(fqdn = mutt_fqdn (0)))
1786 fqdn = NONULL (Hostname);
1788 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1790 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1792 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1793 return (m_strdup(buf));
1796 static RETSIGTYPE alarm_handler (int sig)
1801 /* invoke sendmail in a subshell
1802 path (in) path to program to execute
1803 args (in) arguments to pass to program
1804 msg (in) temp file containing message to send
1805 tempfile (out) if sendmail is put in the background, this points
1806 to the temporary file containing the stdout of the
1809 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1815 mutt_block_signals_system ();
1818 /* we also don't want to be stopped right now */
1819 sigaddset (&set, SIGTSTP);
1820 sigprocmask (SIG_BLOCK, &set, NULL);
1822 if (SendmailWait >= 0) {
1823 char tmp[_POSIX_PATH_MAX];
1826 *tempfile = m_strdup(tmp);
1829 if ((pid = fork ()) == 0) {
1830 struct sigaction act, oldalrm;
1832 /* save parent's ID before setsid() */
1835 /* we want the delivery to continue even after the main process dies,
1836 * so we put ourselves into another session right away
1840 /* next we close all open files */
1841 #if defined(OPEN_MAX)
1842 for (fd = 0; fd < OPEN_MAX; fd++)
1844 #elif defined(_POSIX_OPEN_MAX)
1845 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1853 /* now the second fork() */
1854 if ((pid = fork ()) == 0) {
1855 /* "msg" will be opened as stdin */
1856 if (open (msg, O_RDONLY, 0) < 0) {
1862 if (SendmailWait >= 0) {
1863 /* *tempfile will be opened as stdout */
1864 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1867 /* redirect stderr to *tempfile too */
1872 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1874 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1881 else if (pid == -1) {
1887 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1888 * SendmailWait = 0: wait forever
1889 * SendmailWait < 0: don't wait
1891 if (SendmailWait > 0) {
1893 act.sa_handler = alarm_handler;
1895 /* need to make sure waitpid() is interrupted on SIGALRM */
1896 act.sa_flags = SA_INTERRUPT;
1900 sigemptyset (&act.sa_mask);
1901 sigaction (SIGALRM, &act, &oldalrm);
1902 alarm (SendmailWait);
1904 else if (SendmailWait < 0)
1905 _exit (0xff & EX_OK);
1907 if (waitpid (pid, &st, 0) > 0) {
1908 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1909 if (SendmailWait && st == (0xff & EX_OK)) {
1910 unlink (*tempfile); /* no longer needed */
1915 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1916 if (SendmailWait > 0) {
1922 /* reset alarm; not really needed, but... */
1924 sigaction (SIGALRM, &oldalrm, NULL);
1926 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1927 /* the parent is already dead */
1935 sigprocmask (SIG_UNBLOCK, &set, NULL);
1937 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1938 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1940 st = S_ERR; /* error */
1942 mutt_unblock_signals_system (1);
1947 static const char **
1948 add_args(const char **args, size_t *argslen, size_t *argsmax, ADDRESS * addr)
1950 for (; addr; addr = addr->next) {
1951 /* weed out group mailboxes, since those are for display only */
1952 if (addr->mailbox && !addr->group) {
1953 if (*argslen == *argsmax)
1954 p_realloc(&args, *argsmax += 5);
1955 args[(*argslen)++] = addr->mailbox;
1961 static const char **
1962 add_option(const char **args, size_t *argslen, size_t *argsmax, const char *s)
1964 if (*argslen == *argsmax) {
1965 p_realloc(&args, *argsmax += 5);
1967 args[(*argslen)++] = s;
1971 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
1972 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
1973 const char *msg, /* file containing message */
1975 { /* message contains 8bit chars */
1976 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1977 const char **args = NULL;
1978 size_t argslen = 0, argsmax = 0;
1982 if (option (OPTNEWSSEND)) {
1983 char cmd[LONG_STRING];
1985 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1988 i = nntp_post (msg);
1997 s = m_strdup(Sendmail);
2001 while ((ps = strtok (ps, " "))) {
2002 if (argslen == argsmax)
2003 p_realloc(&args, argsmax += 5);
2006 args[argslen++] = ps;
2008 path = m_strdup(ps);
2009 ps = strrchr (ps, '/');
2014 args[argslen++] = ps;
2021 if (!option (OPTNEWSSEND)) {
2023 if (eightbit && option (OPTUSE8BITMIME))
2024 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
2026 if (option (OPTENVFROM)) {
2030 else if (from && !from->next)
2033 args = add_option (args, &argslen, &argsmax, "-f");
2034 args = add_args (args, &argslen, &argsmax, f);
2038 args = add_option (args, &argslen, &argsmax, "-N");
2039 args = add_option (args, &argslen, &argsmax, DsnNotify);
2042 args = add_option (args, &argslen, &argsmax, "-R");
2043 args = add_option (args, &argslen, &argsmax, DsnReturn);
2045 args = add_option (args, &argslen, &argsmax, "--");
2046 args = add_args (args, &argslen, &argsmax, to);
2047 args = add_args (args, &argslen, &argsmax, cc);
2048 args = add_args (args, &argslen, &argsmax, bcc);
2053 if (argslen == argsmax)
2054 p_realloc(&args, ++argsmax);
2056 args[argslen++] = NULL;
2058 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2060 const char *e = mutt_strsysexit (i);
2062 e = mutt_strsysexit (i);
2063 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2068 if (stat (childout, &st) == 0 && st.st_size > 0)
2069 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2077 p_delete(&childout);
2082 if (i == (EX_OK & 0xff))
2084 else if (i == S_BKG)
2091 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2092 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2093 const char *msg, /* file containing message */
2095 { /* message contains 8bit chars */
2098 if (!option (OPTNEWSSEND))
2101 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2104 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2107 /* appends string 'b' to string 'a', and returns the pointer to the new
2109 char *mutt_append_string (char *a, const char *b)
2111 size_t la = m_strlen(a);
2113 p_realloc(&a, la + m_strlen(b) + 1);
2114 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2118 /* returns 1 if char `c' needs to be quoted to protect from shell
2119 interpretation when executing commands in a subshell */
2120 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2122 /* returns 1 if string `s' contains characters which could cause problems
2123 when used on a command line to execute a command */
2124 int mutt_needs_quote (const char *s)
2127 if (INVALID_CHAR (*s))
2134 /* Quote a string to prevent shell escapes when this string is used on the
2135 command line to send mail. */
2136 char *mutt_quote_string (const char *s)
2141 rlen = m_strlen(s) + 3;
2142 pr = r = p_new(char, rlen);
2145 if (INVALID_CHAR (*s)) {
2148 p_realloc(&r, ++rlen);
2159 /* For postponing (!final) do the necessary encodings only */
2160 void mutt_prepare_envelope (ENVELOPE * env, int final)
2162 char buffer[LONG_STRING];
2165 if (env->bcc && !(env->to || env->cc)) {
2166 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2167 * recipients if there is no To: or Cc: field, so attempt to suppress
2168 * it by using an empty To: field.
2170 env->to = rfc822_new_address ();
2172 env->to->next = rfc822_new_address ();
2175 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2178 env->to->mailbox = m_strdup(buffer);
2181 mutt_set_followup_to (env);
2183 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2184 env->message_id = mutt_gen_msgid ();
2187 /* Take care of 8-bit => 7-bit conversion. */
2188 rfc2047_encode_adrlist (env->to, "To");
2189 rfc2047_encode_adrlist (env->cc, "Cc");
2190 rfc2047_encode_adrlist (env->bcc, "Bcc");
2191 rfc2047_encode_adrlist (env->from, "From");
2192 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2193 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2197 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2200 rfc2047_encode_string (&env->subject);
2202 encode_headers (env->userhdrs);
2205 void mutt_unprepare_envelope (ENVELOPE * env)
2209 for (item = env->userhdrs; item; item = item->next)
2210 rfc2047_decode (&item->data);
2212 rfc822_free_address (&env->mail_followup_to);
2214 /* back conversions */
2215 rfc2047_decode_adrlist (env->to);
2216 rfc2047_decode_adrlist (env->cc);
2217 rfc2047_decode_adrlist (env->bcc);
2218 rfc2047_decode_adrlist (env->from);
2219 rfc2047_decode_adrlist (env->reply_to);
2220 rfc2047_decode (&env->subject);
2223 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2224 const char *resent_from, ADDRESS * env_from)
2228 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2229 MESSAGE *msg = NULL;
2232 /* Try to bounce each message out, aborting if we get any failures. */
2233 for (i = 0; i < Context->msgcount; i++)
2234 if (Context->hdrs[i]->tagged)
2236 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2241 /* If we failed to open a message, return with error */
2242 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2248 mutt_mktemp (tempfile);
2249 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2250 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2252 if (!option (OPTBOUNCEDELIVERED))
2253 ch_flags |= CH_WEED_DELIVERED;
2255 fseeko (fp, h->offset, 0);
2256 fprintf (f, "Resent-From: %s", resent_from);
2257 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2258 if (MsgIdFormat && *MsgIdFormat)
2259 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2260 fputs ("Resent-To: ", f);
2261 mutt_write_address_list (to, f, 11, 0);
2262 mutt_copy_header (fp, h, f, ch_flags, NULL);
2264 mutt_copy_bytes (fp, f, h->content->length);
2267 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2268 h->content->encoding == ENC8BIT);
2272 mx_close_message (&msg);
2277 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2280 const char *fqdn = mutt_fqdn (1);
2281 char resent_from[STRING];
2285 resent_from[0] = '\0';
2286 from = mutt_default_from ();
2289 rfc822_qualify (from, fqdn);
2291 rfc2047_encode_adrlist (from, "Resent-From");
2292 if (mutt_addrlist_to_idna (from, &err)) {
2293 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2296 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2299 unset_option (OPTNEWSSEND);
2302 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2304 rfc822_free_address (&from);
2310 /* given a list of addresses, return a list of unique addresses */
2311 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2313 ADDRESS *top = addr;
2314 ADDRESS **last = ⊤
2319 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2320 if (tmp->mailbox && addr->mailbox &&
2321 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2328 debug_print (2, ("Removing %s\n", addr->mailbox));
2333 rfc822_free_address (&addr);
2346 static void set_noconv_flags (BODY * b, short flag)
2348 for (; b; b = b->next) {
2349 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2350 set_noconv_flags (b->parts, flag);
2351 else if (b->type == TYPETEXT && b->noconv) {
2353 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2355 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2360 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2361 int post, char *fcc)
2365 char tempfile[_POSIX_PATH_MAX];
2366 FILE *tempfp = NULL;
2370 set_noconv_flags (hdr->content, 1);
2372 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2373 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2377 /* We need to add a Content-Length field to avoid problems where a line in
2378 * the message body begins with "From "
2380 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2381 mutt_mktemp (tempfile);
2382 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2383 mutt_perror (tempfile);
2384 mx_close_mailbox (&f, NULL);
2389 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2390 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2391 mx_close_mailbox (&f, NULL);
2395 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2396 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2398 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2401 /* (postponment) if this was a reply of some sort, <msgid> contians the
2402 * Message-ID: of message replied to. Save it using a special X-Mutt-
2403 * header so it can be picked up if the message is recalled at a later
2404 * point in time. This will allow the message to be marked as replied if
2405 * the same mailbox is still open.
2408 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2410 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2411 * it can be picked up when the message is recalled
2414 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2415 fprintf (msg->fp, "Status: RO\n");
2419 /* (postponment) if the mail is to be signed or encrypted, save this info */
2420 if ((WithCrypto & APPLICATION_PGP)
2421 && post && (hdr->security & APPLICATION_PGP)) {
2422 fputs ("X-Mutt-PGP: ", msg->fp);
2423 if (hdr->security & ENCRYPT)
2424 fputc ('E', msg->fp);
2425 if (hdr->security & SIGN) {
2426 fputc ('S', msg->fp);
2427 if (PgpSignAs && *PgpSignAs)
2428 fprintf (msg->fp, "<%s>", PgpSignAs);
2430 if (hdr->security & INLINE)
2431 fputc ('I', msg->fp);
2432 fputc ('\n', msg->fp);
2435 /* (postponment) if the mail is to be signed or encrypted, save this info */
2436 if ((WithCrypto & APPLICATION_SMIME)
2437 && post && (hdr->security & APPLICATION_SMIME)) {
2438 fputs ("X-Mutt-SMIME: ", msg->fp);
2439 if (hdr->security & ENCRYPT) {
2440 fputc ('E', msg->fp);
2441 if (SmimeCryptAlg && *SmimeCryptAlg)
2442 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2444 if (hdr->security & SIGN) {
2445 fputc ('S', msg->fp);
2446 if (SmimeDefaultKey && *SmimeDefaultKey)
2447 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2449 if (hdr->security & INLINE)
2450 fputc ('I', msg->fp);
2451 fputc ('\n', msg->fp);
2455 /* (postponement) if the mail is to be sent through a mixmaster
2456 * chain, save that information
2459 if (post && hdr->chain && hdr->chain) {
2462 fputs ("X-Mutt-Mix:", msg->fp);
2463 for (p = hdr->chain; p; p = p->next)
2464 fprintf (msg->fp, " %s", (char *) p->data);
2466 fputc ('\n', msg->fp);
2471 char sasha[LONG_STRING];
2474 mutt_write_mime_body (hdr->content, tempfp);
2476 /* make sure the last line ends with a newline. Emacs doesn't ensure
2477 * this will happen, and it can cause problems parsing the mailbox
2480 fseeko (tempfp, -1, 2);
2481 if (fgetc (tempfp) != '\n') {
2482 fseeko (tempfp, 0, 2);
2483 fputc ('\n', tempfp);
2487 if (ferror (tempfp)) {
2488 debug_print (1, ("%s: write failed.\n", tempfile));
2491 mx_commit_message (msg, &f); /* XXX - really? */
2492 mx_close_message (&msg);
2493 mx_close_mailbox (&f, NULL);
2497 /* count the number of lines */
2499 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2501 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2502 fprintf (msg->fp, "Lines: %d\n\n", lines);
2504 /* copy the body and clean up */
2506 r = mutt_copy_stream (tempfp, msg->fp);
2507 if (fclose (tempfp) != 0)
2509 /* if there was an error, leave the temp version */
2514 fputc ('\n', msg->fp); /* finish off the header */
2515 r = mutt_write_mime_body (hdr->content, msg->fp);
2518 if (mx_commit_message (msg, &f) != 0)
2520 mx_close_message (&msg);
2521 mx_close_mailbox (&f, NULL);
2524 set_noconv_flags (hdr->content, 0);