2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
5 * This file is part of mutt-ng, see http://www.muttng.org/.
6 * It's licensed under the GNU General Public License,
7 * please see the file GPL in the top level source directory.
16 #include <lib-lib/mem.h>
17 #include <lib-lib/ascii.h>
18 #include <lib-lib/str.h>
19 #include <lib-lib/macros.h>
20 #include <lib-lib/file.h>
24 #include "recvattach.h"
25 #include "mutt_curses.h"
33 #include "mutt_crypt.h"
34 #include "mutt_idna.h"
36 #include "lib/debug.h"
47 #include <sys/utsname.h>
50 # include "mutt_libesmtp.h"
51 #endif /* USE_LIBESMTP */
57 #ifdef HAVE_SYSEXITS_H
59 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
63 /* If you are debugging this file, comment out the following line. */
72 extern char RFC822Specials[];
74 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
76 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
79 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
80 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
81 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
82 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
86 static char MsgIdPfx = 'A';
88 static void transform_to_7bit (BODY * a, FILE * fpin);
90 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
93 char line[77], savechar;
95 while ((c = fgetconv (fc)) != EOF) {
96 /* Wrap the line if needed. */
97 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
98 /* If the last character is "quoted", then be sure to move all three
99 * characters to the next line. Otherwise, just move the last
102 if (line[linelen - 3] == '=') {
103 line[linelen - 3] = 0;
108 line[1] = line[linelen - 2];
109 line[2] = line[linelen - 1];
113 savechar = line[linelen - 1];
114 line[linelen - 1] = '=';
123 /* Escape lines that begin with/only contain "the message separator". */
124 if (linelen == 4 && !str_ncmp ("From", line, 4)) {
125 strfcpy (line, "=46rom", sizeof (line));
128 else if (linelen == 4 && !str_ncmp ("from", line, 4)) {
129 strfcpy (line, "=66rom", sizeof (line));
132 else if (linelen == 1 && line[0] == '.') {
133 strfcpy (line, "=2E", sizeof (line));
138 if (c == '\n' && istext) {
139 /* Check to make sure there is no trailing space on this line. */
141 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
143 sprintf (line + linelen - 1, "=%2.2X",
144 (unsigned char) line[linelen - 1]);
148 int savechar = line[linelen - 1];
150 line[linelen - 1] = '=';
153 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
163 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
164 /* Check to make sure there is enough room for the quoted character.
165 * If not, wrap to the next line.
168 line[linelen++] = '=';
174 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
178 /* Don't worry about wrapping the line here. That will happen during
179 * the next iteration when I'll also know what the next character is.
185 /* Take care of anything left in the buffer */
187 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
188 /* take care of trailing whitespace */
190 sprintf (line + linelen - 1, "=%2.2X",
191 (unsigned char) line[linelen - 1]);
193 savechar = line[linelen - 1];
194 line[linelen - 1] = '=';
198 sprintf (line, "=%2.2X", (unsigned char) savechar);
207 static char b64_buffer[3];
208 static short b64_num;
209 static short b64_linelen;
211 static void b64_flush (FILE * fout)
218 if (b64_linelen >= 72) {
223 for (i = b64_num; i < 3; i++)
224 b64_buffer[i] = '\0';
226 fputc (B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
229 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
234 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
238 fputc (B64Chars[b64_buffer[2] & 0x3f], fout);
243 while (b64_linelen % 4) {
252 static void b64_putc (char c, FILE * fout)
257 b64_buffer[b64_num++] = c;
261 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
265 b64_num = b64_linelen = 0;
267 while ((ch = fgetconv (fc)) != EOF) {
268 if (istext && ch == '\n' && ch1 != '\r')
269 b64_putc ('\r', fout);
277 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
281 while ((ch = fgetconv (fc)) != EOF)
286 int mutt_write_mime_header (BODY * a, FILE * f)
296 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
299 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
301 for (p = a->parameter; p; p = p->next) {
310 tmp = m_strdup(p->value);
311 encode = rfc2231_encode_string (&tmp);
312 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
314 /* Dirty hack to make messages readable by Outlook Express
315 * for the Mac: force quotes around the boundary parameter
316 * even when they aren't needed.
319 if (!ascii_strcasecmp (p->attribute, "boundary")
320 && !strcmp (buffer, tmp))
321 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
325 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
327 if (len + tmplen + 2 > 76) {
336 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
344 fprintf (f, "Content-Description: %s\n", a->description);
346 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
349 if (!(fn = a->d_filename))
355 /* Strip off the leading path... */
356 if ((t = strrchr (fn, '/')))
363 encode = rfc2231_encode_string (&tmp);
364 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
366 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
372 if (a->encoding != ENC7BIT)
373 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
375 /* Do NOT add the terminator here!!! */
376 return (ferror (f) ? -1 : 0);
379 # define write_as_text_part(a) (mutt_is_text_part(a) \
380 || ((WithCrypto & APPLICATION_PGP)\
381 && mutt_is_application_pgp(a)))
383 int mutt_write_mime_body (BODY * a, FILE * f)
385 char *p, boundary[SHORT_STRING];
386 char send_charset[SHORT_STRING];
391 if (a->type == TYPEMULTIPART) {
392 /* First, find the boundary to use */
393 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
394 debug_print (1, ("no boundary parameter found!\n"));
395 mutt_error _("No boundary parameter found! [report this error]");
399 strfcpy (boundary, p, sizeof (boundary));
401 for (t = a->parts; t; t = t->next) {
402 fprintf (f, "\n--%s\n", boundary);
403 if (mutt_write_mime_header (t, f) == -1)
406 if (mutt_write_mime_body (t, f) == -1)
409 fprintf (f, "\n--%s--\n", boundary);
410 return (ferror (f) ? -1 : 0);
413 /* This is pretty gross, but it's the best solution for now... */
414 if ((WithCrypto & APPLICATION_PGP)
415 && a->type == TYPEAPPLICATION
416 && m_strcmp(a->subtype, "pgp-encrypted") == 0) {
417 fputs ("Version: 1\n", f);
421 if ((fpin = fopen (a->filename, "r")) == NULL) {
422 debug_print (1, ("%s no longer exists!\n", a->filename));
423 mutt_error (_("%s no longer exists!"), a->filename);
427 if (a->type == TYPETEXT && (!a->noconv))
428 fc = fgetconv_open (fpin, a->file_charset,
429 mutt_get_body_charset (send_charset,
430 sizeof (send_charset), a), 0);
432 fc = fgetconv_open (fpin, 0, 0, 0);
434 if (a->encoding == ENCQUOTEDPRINTABLE)
435 encode_quoted (fc, f, write_as_text_part (a));
436 else if (a->encoding == ENCBASE64)
437 encode_base64 (fc, f, write_as_text_part (a));
438 else if (a->type == TYPETEXT && (!a->noconv))
439 encode_8bit (fc, f, write_as_text_part (a));
441 mutt_copy_stream (fpin, f);
443 fgetconv_close (&fc);
446 return (ferror (f) ? -1 : 0);
449 #undef write_as_text_part
451 #define BOUNDARYLEN 16
452 void mutt_generate_boundary (PARAMETER ** parm)
454 char rs[BOUNDARYLEN + 1];
459 for (i = 0; i < BOUNDARYLEN; i++)
460 *p++ = B64Chars[LRAND () % sizeof (B64Chars)];
463 mutt_set_parameter ("boundary", rs, parm);
475 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
479 int whitespace = s->whitespace;
481 int linelen = s->linelen;
482 int was_cr = s->was_cr;
484 if (!d) { /* This signals EOF */
487 if (linelen > info->linemax)
488 info->linemax = linelen;
493 for (; dlen; d++, dlen--) {
506 if (linelen > info->linemax)
507 info->linemax = linelen;
522 if (linelen > info->linemax)
523 info->linemax = linelen;
528 else if (ch == '\r') {
536 else if (ch == '\t' || ch == '\f') {
540 else if (ch < 32 || ch == 127)
544 if ((ch == 'F') || (ch == 'f'))
554 if (linelen == 2 && ch != 'r')
556 else if (linelen == 3 && ch != 'o')
558 else if (linelen == 4) {
571 if (ch != ' ' && ch != '\t')
576 s->whitespace = whitespace;
578 s->linelen = linelen;
583 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
584 #define BUGGY_ICONV 1
587 * Find the best charset conversion of the file from fromcode into one
588 * of the tocodes. If successful, set *tocode and CONTENT *info and
589 * return the number of characters converted inexactly. If no
590 * conversion was possible, return -1.
592 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
593 * which would otherwise prevent us from knowing the number of inexact
594 * conversions. Where the candidate target charset is UTF-8 we avoid
595 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
596 * fails with some libraries.
598 * We assume that the output from iconv is never more than 4 times as
599 * long as the input for any pair of charsets we might be interested
602 static size_t convert_file_to (FILE * file, const char *fromcode,
603 int ncodes, const char **tocodes,
604 int *tocode, CONTENT * info)
608 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
611 size_t ibl, obl, ubl, ubl1, n, ret;
614 CONTENT_STATE *states;
617 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
618 if (cd1 == (iconv_t) (-1))
621 cd = p_new(iconv_t, ncodes);
622 score = p_new(size_t, ncodes);
623 states = p_new(CONTENT_STATE, ncodes);
624 infos = p_new(CONTENT, ncodes);
626 for (i = 0; i < ncodes; i++)
627 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
628 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
630 /* Special case for conversion to UTF-8 */
631 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
637 /* Try to fill input buffer */
638 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
641 /* Convert to UTF-8 */
643 ob = bufu, obl = sizeof (bufu);
644 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
645 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
646 if (n == (size_t) (-1) &&
647 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
648 assert (errno == EILSEQ ||
649 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
655 /* Convert from UTF-8 */
656 for (i = 0; i < ncodes; i++)
657 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
658 ub = bufu, ubl = ubl1;
659 ob = bufo, obl = sizeof (bufo);
660 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
661 if (n == (size_t) (-1)) {
662 assert (errno == E2BIG ||
663 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
664 score[i] = (size_t) (-1);
668 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
671 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
672 /* Special case for conversion to UTF-8 */
673 update_content_info (&infos[i], &states[i], bufu, ubl1);
676 /* Save unused input */
677 memmove (bufi, ib, ibl);
678 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
685 /* Find best score */
687 for (i = 0; i < ncodes; i++) {
688 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
689 /* Special case for conversion to UTF-8 */
694 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
696 else if (ret == (size_t) (-1) || score[i] < ret) {
703 if (ret != (size_t) (-1)) {
704 memcpy (info, &infos[*tocode], sizeof (CONTENT));
705 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
709 for (i = 0; i < ncodes; i++)
710 if (cd[i] != (iconv_t) (-1))
722 #endif /* !HAVE_ICONV */
726 * Find the first of the fromcodes that gives a valid conversion and
727 * the best charset conversion of the file into one of the tocodes. If
728 * successful, set *fromcode and *tocode to dynamically allocated
729 * strings, set CONTENT *info, and return the number of characters
730 * converted inexactly. If no conversion was possible, return -1.
732 * Both fromcodes and tocodes may be colon-separated lists of charsets.
733 * However, if fromcode is zero then fromcodes is assumed to be the
734 * name of a single charset even if it contains a colon.
736 static size_t convert_file_from_to (FILE * file,
737 const char *fromcodes,
738 const char *tocodes, char **fromcode,
739 char **tocode, CONTENT * info)
747 /* Count the tocodes */
749 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
750 if ((c1 = strchr (c, ':')) == c)
756 tcode = p_new(char *, ncodes);
757 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
758 if ((c1 = strchr (c, ':')) == c)
760 tcode[i] = str_substrdup (c, c1);
765 /* Try each fromcode in turn */
766 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
767 if ((c1 = strchr (c, ':')) == c)
769 fcode = str_substrdup (c, c1);
771 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
773 if (ret != (size_t) (-1)) {
783 /* There is only one fromcode */
784 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
786 if (ret != (size_t) (-1)) {
793 for (i = 0; i < ncodes; i++)
802 * Analyze the contents of a file to determine which MIME encoding to use.
803 * Also set the body charset, sometimes, or not.
805 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
810 char *fromcode = NULL;
821 if (stat (fname, &sb) == -1) {
822 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
826 if (!S_ISREG (sb.st_mode)) {
827 mutt_error (_("%s isn't a regular file."), fname);
831 if ((fp = fopen (fname, "r")) == NULL) {
832 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
836 info = p_new(CONTENT, 1);
839 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
840 char *chs = mutt_get_parameter ("charset", b->parameter);
841 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
842 FileCharset : Charset) : Charset;
843 if (Charset && (chs || SendCharset) &&
844 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
845 &fromcode, &tocode, info) != (size_t) (-1)) {
847 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
848 mutt_set_parameter ("charset", chsbuf, &b->parameter);
850 b->file_charset = fromcode;
858 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
859 update_content_info (info, &state, buffer, r);
860 update_content_info (info, &state, 0, 0);
864 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
865 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
867 && !mutt_is_us_ascii (Charset) ? Charset :
868 "unknown-8bit"), &b->parameter);
873 /* Given a file with path ``s'', see if there is a registered MIME type.
874 * returns the major MIME type, and copies the subtype to ``d''. First look
875 * for ~/.mime.types, then look in a system mime.types if we can find one.
876 * The longest match is used so that we can match `ps.gz' when `gz' also
880 int mutt_lookup_mime_type (BODY * att, const char *path)
884 char buf[LONG_STRING];
885 char subtype[STRING], xtype[STRING];
887 int szf, sze, cur_sze;
895 szf = m_strlen(path);
897 for (count = 0; count < 4; count++) {
899 * can't use strtok() because we use it in an inner loop below, so use
900 * a switch statement here instead.
904 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
907 strfcpy (buf, SYSCONFDIR "/muttng-mime.types", sizeof (buf));
910 strfcpy (buf, PKGDATADIR "/mime.types", sizeof (buf));
913 strfcpy (buf, SYSCONFDIR "/mime.types", sizeof (buf));
916 debug_print (1, ("Internal error, count = %d.\n", count));
917 goto bye; /* shouldn't happen */
920 if ((f = fopen (buf, "r")) != NULL) {
921 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
922 /* weed out any comments */
923 if ((p = strchr (buf, '#')))
926 /* remove any leading space. */
930 /* position on the next field in this line */
931 if ((p = strpbrk (ct, " \t")) == NULL)
936 /* cycle through the file extensions */
937 while ((p = strtok (p, " \t\n"))) {
939 if ((sze > cur_sze) && (szf >= sze) &&
940 (str_casecmp (path + szf - sze, p) == 0
941 || ascii_strcasecmp (path + szf - sze, p) == 0) && (szf == sze
948 /* get the content-type */
950 if ((p = strchr (ct, '/')) == NULL) {
951 /* malformed line, just skip it. */
956 for (q = p; *q && !ISSPACE (*q); q++);
958 str_substrcpy (subtype, p, q, sizeof (subtype));
960 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
961 strfcpy (xtype, ct, sizeof (xtype));
974 if (type != TYPEOTHER || *xtype != '\0') {
976 str_replace (&att->subtype, subtype);
977 str_replace (&att->xtype, xtype);
983 void mutt_message_to_7bit (BODY * a, FILE * fp)
985 char temp[_POSIX_PATH_MAX];
991 if (!a->filename && fp)
993 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
994 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
999 if (stat (a->filename, &sb) == -1) {
1000 mutt_perror ("stat");
1003 a->length = sb.st_size;
1007 if (!(fpout = safe_fopen (temp, "w+"))) {
1008 mutt_perror ("fopen");
1012 fseeko (fpin, a->offset, 0);
1013 a->parts = mutt_parse_messageRFC822 (fpin, a);
1015 transform_to_7bit (a->parts, fpin);
1017 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1018 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1020 fputs ("MIME-Version: 1.0\n", fpout);
1021 mutt_write_mime_header (a->parts, fpout);
1022 fputc ('\n', fpout);
1023 mutt_write_mime_body (a->parts, fpout);
1035 a->encoding = ENC7BIT;
1036 a->d_filename = a->filename;
1037 if (a->filename && a->unlink)
1038 unlink (a->filename);
1039 a->filename = m_strdup(temp);
1041 if (stat (a->filename, &sb) == -1) {
1042 mutt_perror ("stat");
1045 a->length = sb.st_size;
1046 mutt_free_body (&a->parts);
1047 a->hdr->content = NULL;
1050 static void transform_to_7bit (BODY * a, FILE * fpin)
1052 char buff[_POSIX_PATH_MAX];
1057 for (; a; a = a->next) {
1058 if (a->type == TYPEMULTIPART) {
1059 if (a->encoding != ENC7BIT)
1060 a->encoding = ENC7BIT;
1062 transform_to_7bit (a->parts, fpin);
1064 else if (mutt_is_message_type (a->type, a->subtype)) {
1065 mutt_message_to_7bit (a, fpin);
1069 a->force_charset = 1;
1072 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1073 mutt_perror ("fopen");
1077 mutt_decode_attachment (a, &s);
1079 a->d_filename = a->filename;
1080 a->filename = m_strdup(buff);
1082 if (stat (a->filename, &sb) == -1) {
1083 mutt_perror ("stat");
1086 a->length = sb.st_size;
1088 mutt_update_encoding (a);
1089 if (a->encoding == ENC8BIT)
1090 a->encoding = ENCQUOTEDPRINTABLE;
1091 else if (a->encoding == ENCBINARY)
1092 a->encoding = ENCBASE64;
1097 /* determine which Content-Transfer-Encoding to use */
1098 static void mutt_set_encoding (BODY * b, CONTENT * info)
1100 char send_charset[SHORT_STRING];
1102 if (b->type == TYPETEXT) {
1104 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1105 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1106 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1107 b->encoding = ENCQUOTEDPRINTABLE;
1108 else if (info->hibin)
1109 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1111 b->encoding = ENC7BIT;
1113 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1114 if (info->lobin || info->hibin) {
1115 if (option (OPTALLOW8BIT) && !info->lobin)
1116 b->encoding = ENC8BIT;
1118 mutt_message_to_7bit (b, NULL);
1121 b->encoding = ENC7BIT;
1123 else if (b->type == TYPEAPPLICATION
1124 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1125 b->encoding = ENC7BIT;
1128 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1129 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1132 /* Determine which encoding is smaller */
1133 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1134 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1135 b->encoding = ENCBASE64;
1137 b->encoding = ENCQUOTEDPRINTABLE;
1141 b->encoding = ENC7BIT;
1145 void mutt_stamp_attachment (BODY * a)
1147 a->stamp = time (NULL);
1150 /* Get a body's character set */
1152 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1156 if (b && b->type != TYPETEXT)
1160 p = mutt_get_parameter ("charset", b->parameter);
1163 mutt_canonical_charset (d, dlen, NONULL (p));
1165 strfcpy (d, "us-ascii", dlen);
1171 /* Assumes called from send mode where BODY->filename points to actual file */
1172 void mutt_update_encoding (BODY * a)
1175 char chsbuff[STRING];
1177 /* override noconv when it's us-ascii */
1178 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1181 if (!a->force_charset && !a->noconv)
1182 mutt_delete_parameter ("charset", &a->parameter);
1184 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1187 mutt_set_encoding (a, info);
1188 mutt_stamp_attachment (a);
1190 p_delete(&a->content);
1195 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1197 char buffer[LONG_STRING];
1200 int cmflags, chflags;
1201 int pgp = WithCrypto ? hdr->security : 0;
1204 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1205 (hdr->security & ENCRYPT)) {
1206 if (!crypt_valid_passphrase (hdr->security))
1211 mutt_mktemp (buffer);
1212 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1215 body = mutt_new_body ();
1216 body->type = TYPEMESSAGE;
1217 body->subtype = m_strdup("rfc822");
1218 body->filename = m_strdup(buffer);
1221 body->disposition = DISPINLINE;
1224 mutt_parse_mime_message (ctx, hdr);
1229 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1230 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1231 chflags |= CH_MIME | CH_TXTPLAIN;
1232 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1233 if ((WithCrypto & APPLICATION_PGP))
1235 if ((WithCrypto & APPLICATION_SMIME))
1236 pgp &= ~SMIMEENCRYPT;
1238 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1239 if ((WithCrypto & APPLICATION_PGP)
1240 && mutt_is_multipart_encrypted (hdr->content)) {
1241 chflags |= CH_MIME | CH_NONEWLINE;
1242 cmflags = M_CM_DECODE_PGP;
1245 else if ((WithCrypto & APPLICATION_PGP)
1246 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1247 chflags |= CH_MIME | CH_TXTPLAIN;
1248 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1251 else if ((WithCrypto & APPLICATION_SMIME)
1252 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1253 chflags |= CH_MIME | CH_TXTPLAIN;
1254 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1255 pgp &= ~SMIMEENCRYPT;
1259 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1264 body->hdr = mutt_new_header ();
1265 body->hdr->offset = 0;
1266 /* we don't need the user headers here */
1267 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1269 body->hdr->security = pgp;
1270 mutt_update_encoding (body);
1271 body->parts = body->hdr->content;
1278 BODY *mutt_make_file_attach (const char *path)
1283 att = mutt_new_body ();
1284 att->filename = m_strdup(path);
1286 /* Attempt to determine the appropriate content-type based on the filename
1293 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1294 path)) != TYPEOTHER || *xbuf != '\0') {
1296 att->subtype = m_strdup(buf);
1297 att->xtype = m_strdup(xbuf);
1302 mutt_lookup_mime_type (att, path);
1306 if ((info = mutt_get_content_info (path, att)) == NULL) {
1307 mutt_free_body (&att);
1311 if (!att->subtype) {
1312 if (info->lobin == 0
1313 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1315 * Statistically speaking, there should be more than 10% "lobin"
1316 * chars if this is really a binary file...
1318 att->type = TYPETEXT;
1319 att->subtype = m_strdup("plain");
1322 att->type = TYPEAPPLICATION;
1323 att->subtype = m_strdup("octet-stream");
1327 mutt_update_encoding (att);
1331 static int get_toplevel_encoding (BODY * a)
1335 for (; a; a = a->next) {
1336 if (a->encoding == ENCBINARY)
1338 else if (a->encoding == ENC8BIT)
1345 BODY *mutt_make_multipart (BODY * b)
1349 new = mutt_new_body ();
1350 new->type = TYPEMULTIPART;
1351 new->subtype = m_strdup("mixed");
1352 new->encoding = get_toplevel_encoding (b);
1353 mutt_generate_boundary (&new->parameter);
1355 new->disposition = DISPINLINE;
1361 /* remove the multipart body if it exists */
1362 BODY *mutt_remove_multipart (BODY * b)
1370 mutt_free_body (&t);
1375 char *mutt_make_date (char *s, size_t len)
1377 time_t t = time (NULL);
1378 struct tm *l = localtime (&t);
1379 time_t tz = mutt_local_tz (t);
1383 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1384 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1385 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1386 (int) tz / 60, (int) abs (tz) % 60);
1390 /* wrapper around mutt_write_address() so we can handle very large
1391 recipient lists without needing a huge temporary buffer in memory */
1392 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1396 char buf[LONG_STRING];
1404 rfc822_write_address (buf, sizeof (buf), adr, display);
1405 len = m_strlen(buf);
1406 if (count && linelen + len > 74) {
1408 linelen = len + 8; /* tab is usually about 8 spaces... */
1411 if (count && adr->mailbox) {
1419 if (!adr->group && adr->next && adr->next->mailbox) {
1429 /* arbitrary number of elements to grow the array by */
1434 /* need to write the list in reverse because they are stored in reverse order
1435 * when parsed to speed up threading
1437 void mutt_write_references (LIST * r, FILE * f)
1440 int refcnt = 0, refmax = 0;
1442 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1443 if (refcnt == refmax)
1444 p_realloc(&ref, refmax += REF_INC);
1448 while (refcnt-- > 0) {
1450 fputs (ref[refcnt]->data, f);
1456 /* Note: all RFC2047 encoding should be done outside of this routine, except
1457 * for the "real name." This will allow this routine to be used more than
1458 * once, if necessary.
1460 * Likewise, all IDN processing should happen outside of this routine.
1462 * mode == 1 => "lite" mode (used for edit_hdrs)
1463 * mode == 0 => normal mode. write full header + MIME headers
1464 * mode == -1 => write just the envelope info (used for postponing messages)
1466 * privacy != 0 => will omit any headers which may identify the user.
1467 * Output generated is suitable for being sent through
1468 * anonymous remailer chains.
1472 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1473 int mode, int privacy)
1475 char buffer[LONG_STRING];
1477 LIST *tmp = env->userhdrs;
1478 int has_agent = 0; /* user defined user-agent header field exists */
1479 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1482 if (!option (OPTNEWSSEND))
1484 if (mode == 0 && !privacy)
1485 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1487 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1489 /* OPTUSEFROM is not consulted here so that we can still write a From:
1490 * field if the user sets it with the `my_hdr' command
1492 if (env->from && !privacy) {
1494 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1495 fprintf (fp, "From: %s\n", buffer);
1500 mutt_write_address_list (env->to, fp, 4, 0);
1504 if (!option (OPTNEWSSEND))
1506 if (EDIT_HEADER("To:"))
1507 fputs ("To:\n", fp);
1511 mutt_write_address_list (env->cc, fp, 4, 0);
1515 if (!option (OPTNEWSSEND))
1517 if (EDIT_HEADER("Cc:"))
1518 fputs ("Cc:\n", fp);
1521 if (mode != 0 || option (OPTWRITEBCC)) {
1522 fputs ("Bcc: ", fp);
1523 mutt_write_address_list (env->bcc, fp, 5, 0);
1528 if (!option (OPTNEWSSEND))
1530 if (EDIT_HEADER("Bcc:"))
1531 fputs ("Bcc:\n", fp);
1534 if (env->newsgroups)
1535 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1536 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1537 fputs ("Newsgroups:\n", fp);
1539 if (env->followup_to)
1540 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1541 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1542 fputs ("Followup-To:\n", fp);
1544 if (env->x_comment_to)
1545 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1546 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1547 EDIT_HEADER("X-Comment-To:"))
1548 fputs ("X-Comment-To:\n", fp);
1552 fprintf (fp, "Subject: %s\n", env->subject);
1553 else if (mode == 1 && EDIT_HEADER("Subject:"))
1554 fputs ("Subject:\n", fp);
1556 /* save message id if the user has set it */
1557 if (env->message_id && !privacy)
1558 fprintf (fp, "Message-ID: %s\n", env->message_id);
1560 if (env->reply_to) {
1561 fputs ("Reply-To: ", fp);
1562 mutt_write_address_list (env->reply_to, fp, 10, 0);
1564 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1565 fputs ("Reply-To:\n", fp);
1567 if (env->mail_followup_to)
1569 if (!option (OPTNEWSSEND))
1572 fputs ("Mail-Followup-To: ", fp);
1573 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1577 if (env->references) {
1578 fputs ("References:", fp);
1579 mutt_write_references (env->references, fp);
1583 /* Add the MIME headers */
1584 fputs ("MIME-Version: 1.0\n", fp);
1585 mutt_write_mime_header (attach, fp);
1588 if (env->in_reply_to) {
1589 fputs ("In-Reply-To:", fp);
1590 mutt_write_references (env->in_reply_to, fp);
1596 /* Add any user defined headers */
1597 for (; tmp; tmp = tmp->next) {
1598 if ((p = strchr (tmp->data, ':'))) {
1602 continue; /* don't emit empty fields. */
1604 /* check to see if the user has overridden the user-agent field */
1605 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1611 fputs (tmp->data, fp);
1616 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1619 if (OperatingSystem != NULL) {
1620 os = OperatingSystem;
1623 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1625 /* Add a vanity header */
1626 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1629 list_del (&hdrs, (list_del_t*)xmemfree);
1631 return (ferror (fp) == 0 ? 0 : -1);
1634 static void encode_headers (LIST * h)
1640 for (; h; h = h->next) {
1641 if (!(p = strchr (h->data, ':')))
1652 rfc2047_encode_string (&tmp);
1653 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1655 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1661 const char *mutt_fqdn (short may_hide_host)
1665 if (Fqdn && Fqdn[0] != '@') {
1668 if (may_hide_host && option (OPTHIDDENHOST)) {
1669 if ((p = strchr (Fqdn, '.')))
1672 /* sanity check: don't hide the host if
1673 * the fqdn is something like detebe.org.
1676 if (!p || !(q = strchr (p, '.')))
1684 static char mutt_normalized_char (char c)
1688 if (strchr (".!#$%&'*+-/=?^_`{|}~", c))
1690 return '.'; /* normalized character (we're stricter than RFC2822, 3.6.4) */
1693 static void mutt_gen_localpart (char *buf, unsigned int len, char *fmt)
1697 char tmp[SHORT_STRING];
1704 for (; *fmt; ++fmt) {
1710 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mday);
1711 str_ncat (buf, len, tmp, 2);
1714 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_hour);
1715 str_ncat (buf, len, tmp, 2);
1718 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mon + 1);
1719 str_ncat (buf, len, tmp, 2);
1722 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_min);
1723 str_ncat (buf, len, tmp, 2);
1726 snprintf (tmp, sizeof (tmp), "%lo", (unsigned long) now);
1727 str_ncat (buf, len, tmp, m_strlen(tmp));
1730 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) getpid ());
1731 str_ncat (buf, len, tmp, m_strlen(tmp));
1734 snprintf (tmp, sizeof (tmp), "%c", MsgIdPfx);
1735 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1736 str_ncat (buf, len, tmp, 1);
1739 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) rand ());
1740 str_ncat (buf, len, tmp, m_strlen(tmp));
1743 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) rand ());
1744 str_ncat (buf, len, tmp, m_strlen(tmp));
1747 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_sec);
1748 str_ncat (buf, len, tmp, 2);
1751 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) now);
1752 str_ncat (buf, len, tmp, m_strlen(tmp));
1755 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) now);
1756 str_ncat (buf, len, tmp, m_strlen(tmp));
1759 snprintf (tmp, sizeof (tmp), "%04d", tm->tm_year + 1900); /* this will break in the year 10000 ;-) */
1760 str_ncat (buf, len, tmp, 4);
1763 str_ncat (buf, len, "%", 1);
1766 str_ncat (buf, len, ".", 1); /* invalid formats are replaced by '.' */
1773 c = mutt_normalized_char (*fmt); /* @todo: filter out invalid characters */
1774 str_ncat (buf, len, &c, 1);
1779 char *mutt_gen_msgid (void)
1781 char buf[SHORT_STRING];
1782 char localpart[SHORT_STRING];
1783 unsigned int localpart_length;
1786 if (!(fqdn = mutt_fqdn (0)))
1787 fqdn = NONULL (Hostname);
1789 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1791 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1793 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1794 return (m_strdup(buf));
1797 static RETSIGTYPE alarm_handler (int sig)
1802 /* invoke sendmail in a subshell
1803 path (in) path to program to execute
1804 args (in) arguments to pass to program
1805 msg (in) temp file containing message to send
1806 tempfile (out) if sendmail is put in the background, this points
1807 to the temporary file containing the stdout of the
1810 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1816 mutt_block_signals_system ();
1819 /* we also don't want to be stopped right now */
1820 sigaddset (&set, SIGTSTP);
1821 sigprocmask (SIG_BLOCK, &set, NULL);
1823 if (SendmailWait >= 0) {
1824 char tmp[_POSIX_PATH_MAX];
1827 *tempfile = m_strdup(tmp);
1830 if ((pid = fork ()) == 0) {
1831 struct sigaction act, oldalrm;
1833 /* save parent's ID before setsid() */
1836 /* we want the delivery to continue even after the main process dies,
1837 * so we put ourselves into another session right away
1841 /* next we close all open files */
1842 #if defined(OPEN_MAX)
1843 for (fd = 0; fd < OPEN_MAX; fd++)
1845 #elif defined(_POSIX_OPEN_MAX)
1846 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1854 /* now the second fork() */
1855 if ((pid = fork ()) == 0) {
1856 /* "msg" will be opened as stdin */
1857 if (open (msg, O_RDONLY, 0) < 0) {
1863 if (SendmailWait >= 0) {
1864 /* *tempfile will be opened as stdout */
1865 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1868 /* redirect stderr to *tempfile too */
1873 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1875 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1882 else if (pid == -1) {
1888 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1889 * SendmailWait = 0: wait forever
1890 * SendmailWait < 0: don't wait
1892 if (SendmailWait > 0) {
1894 act.sa_handler = alarm_handler;
1896 /* need to make sure waitpid() is interrupted on SIGALRM */
1897 act.sa_flags = SA_INTERRUPT;
1901 sigemptyset (&act.sa_mask);
1902 sigaction (SIGALRM, &act, &oldalrm);
1903 alarm (SendmailWait);
1905 else if (SendmailWait < 0)
1906 _exit (0xff & EX_OK);
1908 if (waitpid (pid, &st, 0) > 0) {
1909 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1910 if (SendmailWait && st == (0xff & EX_OK)) {
1911 unlink (*tempfile); /* no longer needed */
1916 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1917 if (SendmailWait > 0) {
1923 /* reset alarm; not really needed, but... */
1925 sigaction (SIGALRM, &oldalrm, NULL);
1927 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1928 /* the parent is already dead */
1936 sigprocmask (SIG_UNBLOCK, &set, NULL);
1938 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1939 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1941 st = S_ERR; /* error */
1943 mutt_unblock_signals_system (1);
1948 static const char **
1949 add_args(const char **args, size_t *argslen, size_t *argsmax, ADDRESS * addr)
1951 for (; addr; addr = addr->next) {
1952 /* weed out group mailboxes, since those are for display only */
1953 if (addr->mailbox && !addr->group) {
1954 if (*argslen == *argsmax)
1955 p_realloc(&args, *argsmax += 5);
1956 args[(*argslen)++] = addr->mailbox;
1962 static const char **
1963 add_option(const char **args, size_t *argslen, size_t *argsmax, const char *s)
1965 if (*argslen == *argsmax) {
1966 p_realloc(&args, *argsmax += 5);
1968 args[(*argslen)++] = s;
1972 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
1973 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
1974 const char *msg, /* file containing message */
1976 { /* message contains 8bit chars */
1977 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1978 const char **args = NULL;
1979 size_t argslen = 0, argsmax = 0;
1983 if (option (OPTNEWSSEND)) {
1984 char cmd[LONG_STRING];
1986 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1989 i = nntp_post (msg);
1998 s = m_strdup(Sendmail);
2002 while ((ps = strtok (ps, " "))) {
2003 if (argslen == argsmax)
2004 p_realloc(&args, argsmax += 5);
2007 args[argslen++] = ps;
2009 path = m_strdup(ps);
2010 ps = strrchr (ps, '/');
2015 args[argslen++] = ps;
2022 if (!option (OPTNEWSSEND)) {
2024 if (eightbit && option (OPTUSE8BITMIME))
2025 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
2027 if (option (OPTENVFROM)) {
2031 else if (from && !from->next)
2034 args = add_option (args, &argslen, &argsmax, "-f");
2035 args = add_args (args, &argslen, &argsmax, f);
2039 args = add_option (args, &argslen, &argsmax, "-N");
2040 args = add_option (args, &argslen, &argsmax, DsnNotify);
2043 args = add_option (args, &argslen, &argsmax, "-R");
2044 args = add_option (args, &argslen, &argsmax, DsnReturn);
2046 args = add_option (args, &argslen, &argsmax, "--");
2047 args = add_args (args, &argslen, &argsmax, to);
2048 args = add_args (args, &argslen, &argsmax, cc);
2049 args = add_args (args, &argslen, &argsmax, bcc);
2054 if (argslen == argsmax)
2055 p_realloc(&args, ++argsmax);
2057 args[argslen++] = NULL;
2059 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2061 const char *e = mutt_strsysexit (i);
2063 e = mutt_strsysexit (i);
2064 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2069 if (stat (childout, &st) == 0 && st.st_size > 0)
2070 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2078 p_delete(&childout);
2083 if (i == (EX_OK & 0xff))
2085 else if (i == S_BKG)
2092 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2093 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2094 const char *msg, /* file containing message */
2096 { /* message contains 8bit chars */
2099 if (!option (OPTNEWSSEND))
2102 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2105 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2108 /* appends string 'b' to string 'a', and returns the pointer to the new
2110 char *mutt_append_string (char *a, const char *b)
2112 size_t la = m_strlen(a);
2114 p_realloc(&a, la + m_strlen(b) + 1);
2115 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2119 /* returns 1 if char `c' needs to be quoted to protect from shell
2120 interpretation when executing commands in a subshell */
2121 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2123 /* returns 1 if string `s' contains characters which could cause problems
2124 when used on a command line to execute a command */
2125 int mutt_needs_quote (const char *s)
2128 if (INVALID_CHAR (*s))
2135 /* Quote a string to prevent shell escapes when this string is used on the
2136 command line to send mail. */
2137 char *mutt_quote_string (const char *s)
2142 rlen = m_strlen(s) + 3;
2143 pr = r = p_new(char, rlen);
2146 if (INVALID_CHAR (*s)) {
2149 p_realloc(&r, ++rlen);
2160 /* For postponing (!final) do the necessary encodings only */
2161 void mutt_prepare_envelope (ENVELOPE * env, int final)
2163 char buffer[LONG_STRING];
2166 if (env->bcc && !(env->to || env->cc)) {
2167 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2168 * recipients if there is no To: or Cc: field, so attempt to suppress
2169 * it by using an empty To: field.
2171 env->to = rfc822_new_address ();
2173 env->to->next = rfc822_new_address ();
2176 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2179 env->to->mailbox = m_strdup(buffer);
2182 mutt_set_followup_to (env);
2184 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2185 env->message_id = mutt_gen_msgid ();
2188 /* Take care of 8-bit => 7-bit conversion. */
2189 rfc2047_encode_adrlist (env->to, "To");
2190 rfc2047_encode_adrlist (env->cc, "Cc");
2191 rfc2047_encode_adrlist (env->bcc, "Bcc");
2192 rfc2047_encode_adrlist (env->from, "From");
2193 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2194 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2198 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2201 rfc2047_encode_string (&env->subject);
2203 encode_headers (env->userhdrs);
2206 void mutt_unprepare_envelope (ENVELOPE * env)
2210 for (item = env->userhdrs; item; item = item->next)
2211 rfc2047_decode (&item->data);
2213 rfc822_free_address (&env->mail_followup_to);
2215 /* back conversions */
2216 rfc2047_decode_adrlist (env->to);
2217 rfc2047_decode_adrlist (env->cc);
2218 rfc2047_decode_adrlist (env->bcc);
2219 rfc2047_decode_adrlist (env->from);
2220 rfc2047_decode_adrlist (env->reply_to);
2221 rfc2047_decode (&env->subject);
2224 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2225 const char *resent_from, ADDRESS * env_from)
2229 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2230 MESSAGE *msg = NULL;
2233 /* Try to bounce each message out, aborting if we get any failures. */
2234 for (i = 0; i < Context->msgcount; i++)
2235 if (Context->hdrs[i]->tagged)
2237 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2242 /* If we failed to open a message, return with error */
2243 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2249 mutt_mktemp (tempfile);
2250 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2251 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2253 if (!option (OPTBOUNCEDELIVERED))
2254 ch_flags |= CH_WEED_DELIVERED;
2256 fseeko (fp, h->offset, 0);
2257 fprintf (f, "Resent-From: %s", resent_from);
2258 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2259 if (MsgIdFormat && *MsgIdFormat)
2260 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2261 fputs ("Resent-To: ", f);
2262 mutt_write_address_list (to, f, 11, 0);
2263 mutt_copy_header (fp, h, f, ch_flags, NULL);
2265 mutt_copy_bytes (fp, f, h->content->length);
2268 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2269 h->content->encoding == ENC8BIT);
2273 mx_close_message (&msg);
2278 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2281 const char *fqdn = mutt_fqdn (1);
2282 char resent_from[STRING];
2286 resent_from[0] = '\0';
2287 from = mutt_default_from ();
2290 rfc822_qualify (from, fqdn);
2292 rfc2047_encode_adrlist (from, "Resent-From");
2293 if (mutt_addrlist_to_idna (from, &err)) {
2294 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2297 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2300 unset_option (OPTNEWSSEND);
2303 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2305 rfc822_free_address (&from);
2311 /* given a list of addresses, return a list of unique addresses */
2312 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2314 ADDRESS *top = addr;
2315 ADDRESS **last = ⊤
2320 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2321 if (tmp->mailbox && addr->mailbox &&
2322 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2329 debug_print (2, ("Removing %s\n", addr->mailbox));
2334 rfc822_free_address (&addr);
2347 static void set_noconv_flags (BODY * b, short flag)
2349 for (; b; b = b->next) {
2350 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2351 set_noconv_flags (b->parts, flag);
2352 else if (b->type == TYPETEXT && b->noconv) {
2354 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2356 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2361 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2362 int post, char *fcc)
2366 char tempfile[_POSIX_PATH_MAX];
2367 FILE *tempfp = NULL;
2371 set_noconv_flags (hdr->content, 1);
2373 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2374 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2378 /* We need to add a Content-Length field to avoid problems where a line in
2379 * the message body begins with "From "
2381 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2382 mutt_mktemp (tempfile);
2383 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2384 mutt_perror (tempfile);
2385 mx_close_mailbox (&f, NULL);
2390 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2391 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2392 mx_close_mailbox (&f, NULL);
2396 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2397 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2399 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2402 /* (postponment) if this was a reply of some sort, <msgid> contians the
2403 * Message-ID: of message replied to. Save it using a special X-Mutt-
2404 * header so it can be picked up if the message is recalled at a later
2405 * point in time. This will allow the message to be marked as replied if
2406 * the same mailbox is still open.
2409 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2411 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2412 * it can be picked up when the message is recalled
2415 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2416 fprintf (msg->fp, "Status: RO\n");
2420 /* (postponment) if the mail is to be signed or encrypted, save this info */
2421 if ((WithCrypto & APPLICATION_PGP)
2422 && post && (hdr->security & APPLICATION_PGP)) {
2423 fputs ("X-Mutt-PGP: ", msg->fp);
2424 if (hdr->security & ENCRYPT)
2425 fputc ('E', msg->fp);
2426 if (hdr->security & SIGN) {
2427 fputc ('S', msg->fp);
2428 if (PgpSignAs && *PgpSignAs)
2429 fprintf (msg->fp, "<%s>", PgpSignAs);
2431 if (hdr->security & INLINE)
2432 fputc ('I', msg->fp);
2433 fputc ('\n', msg->fp);
2436 /* (postponment) if the mail is to be signed or encrypted, save this info */
2437 if ((WithCrypto & APPLICATION_SMIME)
2438 && post && (hdr->security & APPLICATION_SMIME)) {
2439 fputs ("X-Mutt-SMIME: ", msg->fp);
2440 if (hdr->security & ENCRYPT) {
2441 fputc ('E', msg->fp);
2442 if (SmimeCryptAlg && *SmimeCryptAlg)
2443 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2445 if (hdr->security & SIGN) {
2446 fputc ('S', msg->fp);
2447 if (SmimeDefaultKey && *SmimeDefaultKey)
2448 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2450 if (hdr->security & INLINE)
2451 fputc ('I', msg->fp);
2452 fputc ('\n', msg->fp);
2456 /* (postponement) if the mail is to be sent through a mixmaster
2457 * chain, save that information
2460 if (post && hdr->chain && hdr->chain) {
2463 fputs ("X-Mutt-Mix:", msg->fp);
2464 for (p = hdr->chain; p; p = p->next)
2465 fprintf (msg->fp, " %s", (char *) p->data);
2467 fputc ('\n', msg->fp);
2472 char sasha[LONG_STRING];
2475 mutt_write_mime_body (hdr->content, tempfp);
2477 /* make sure the last line ends with a newline. Emacs doesn't ensure
2478 * this will happen, and it can cause problems parsing the mailbox
2481 fseeko (tempfp, -1, 2);
2482 if (fgetc (tempfp) != '\n') {
2483 fseeko (tempfp, 0, 2);
2484 fputc ('\n', tempfp);
2488 if (ferror (tempfp)) {
2489 debug_print (1, ("%s: write failed.\n", tempfile));
2492 mx_commit_message (msg, &f); /* XXX - really? */
2493 mx_close_message (&msg);
2494 mx_close_mailbox (&f, NULL);
2498 /* count the number of lines */
2500 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2502 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2503 fprintf (msg->fp, "Lines: %d\n\n", lines);
2505 /* copy the body and clean up */
2507 r = mutt_copy_stream (tempfp, msg->fp);
2508 if (fclose (tempfp) != 0)
2510 /* if there was an error, leave the temp version */
2515 fputc ('\n', msg->fp); /* finish off the header */
2516 r = mutt_write_mime_body (hdr->content, msg->fp);
2519 if (mx_commit_message (msg, &f) != 0)
2521 mx_close_message (&msg);
2522 mx_close_mailbox (&f, NULL);
2525 set_noconv_flags (hdr->content, 0);