2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
5 * This file is part of mutt-ng, see http://www.muttng.org/.
6 * It's licensed under the GNU General Public License,
7 * please see the file GPL in the top level source directory.
19 #include "recvattach.h"
20 #include "mutt_curses.h"
28 #include "mutt_crypt.h"
29 #include "mutt_idna.h"
34 #include "lib/debug.h"
45 #include <sys/utsname.h>
48 # include "mutt_libesmtp.h"
49 #endif /* USE_LIBESMTP */
55 #ifdef HAVE_SYSEXITS_H
57 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
61 /* If you are debugging this file, comment out the following line. */
70 extern char RFC822Specials[];
72 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
74 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
77 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
78 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
79 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
80 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
84 static char MsgIdPfx = 'A';
86 static void transform_to_7bit (BODY * a, FILE * fpin);
88 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
91 char line[77], savechar;
93 while ((c = fgetconv (fc)) != EOF) {
94 /* Wrap the line if needed. */
95 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
96 /* If the last character is "quoted", then be sure to move all three
97 * characters to the next line. Otherwise, just move the last
100 if (line[linelen - 3] == '=') {
101 line[linelen - 3] = 0;
106 line[1] = line[linelen - 2];
107 line[2] = line[linelen - 1];
111 savechar = line[linelen - 1];
112 line[linelen - 1] = '=';
121 /* Escape lines that begin with/only contain "the message separator". */
122 if (linelen == 4 && !str_ncmp ("From", line, 4)) {
123 strfcpy (line, "=46rom", sizeof (line));
126 else if (linelen == 4 && !str_ncmp ("from", line, 4)) {
127 strfcpy (line, "=66rom", sizeof (line));
130 else if (linelen == 1 && line[0] == '.') {
131 strfcpy (line, "=2E", sizeof (line));
136 if (c == '\n' && istext) {
137 /* Check to make sure there is no trailing space on this line. */
139 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
141 sprintf (line + linelen - 1, "=%2.2X",
142 (unsigned char) line[linelen - 1]);
146 int savechar = line[linelen - 1];
148 line[linelen - 1] = '=';
151 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
161 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
162 /* Check to make sure there is enough room for the quoted character.
163 * If not, wrap to the next line.
166 line[linelen++] = '=';
172 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
176 /* Don't worry about wrapping the line here. That will happen during
177 * the next iteration when I'll also know what the next character is.
183 /* Take care of anything left in the buffer */
185 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
186 /* take care of trailing whitespace */
188 sprintf (line + linelen - 1, "=%2.2X",
189 (unsigned char) line[linelen - 1]);
191 savechar = line[linelen - 1];
192 line[linelen - 1] = '=';
196 sprintf (line, "=%2.2X", (unsigned char) savechar);
205 static char b64_buffer[3];
206 static short b64_num;
207 static short b64_linelen;
209 static void b64_flush (FILE * fout)
216 if (b64_linelen >= 72) {
221 for (i = b64_num; i < 3; i++)
222 b64_buffer[i] = '\0';
224 fputc (B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
227 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
232 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
236 fputc (B64Chars[b64_buffer[2] & 0x3f], fout);
241 while (b64_linelen % 4) {
250 static void b64_putc (char c, FILE * fout)
255 b64_buffer[b64_num++] = c;
259 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
263 b64_num = b64_linelen = 0;
265 while ((ch = fgetconv (fc)) != EOF) {
266 if (istext && ch == '\n' && ch1 != '\r')
267 b64_putc ('\r', fout);
275 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
279 while ((ch = fgetconv (fc)) != EOF)
284 int mutt_write_mime_header (BODY * a, FILE * f)
294 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
297 len = 25 + str_len (a->subtype); /* approximate len. of content-type */
299 for (p = a->parameter; p; p = p->next) {
308 tmp = str_dup (p->value);
309 encode = rfc2231_encode_string (&tmp);
310 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
312 /* Dirty hack to make messages readable by Outlook Express
313 * for the Mac: force quotes around the boundary parameter
314 * even when they aren't needed.
317 if (!ascii_strcasecmp (p->attribute, "boundary")
318 && !strcmp (buffer, tmp))
319 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
323 tmplen = str_len (buffer) + str_len (p->attribute) + 1;
325 if (len + tmplen + 2 > 76) {
334 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
342 fprintf (f, "Content-Description: %s\n", a->description);
344 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
347 if (!(fn = a->d_filename))
353 /* Strip off the leading path... */
354 if ((t = strrchr (fn, '/')))
361 encode = rfc2231_encode_string (&tmp);
362 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
364 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
370 if (a->encoding != ENC7BIT)
371 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
373 /* Do NOT add the terminator here!!! */
374 return (ferror (f) ? -1 : 0);
377 # define write_as_text_part(a) (mutt_is_text_part(a) \
378 || ((WithCrypto & APPLICATION_PGP)\
379 && mutt_is_application_pgp(a)))
381 int mutt_write_mime_body (BODY * a, FILE * f)
383 char *p, boundary[SHORT_STRING];
384 char send_charset[SHORT_STRING];
389 if (a->type == TYPEMULTIPART) {
390 /* First, find the boundary to use */
391 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
392 debug_print (1, ("no boundary parameter found!\n"));
393 mutt_error _("No boundary parameter found! [report this error]");
397 strfcpy (boundary, p, sizeof (boundary));
399 for (t = a->parts; t; t = t->next) {
400 fprintf (f, "\n--%s\n", boundary);
401 if (mutt_write_mime_header (t, f) == -1)
404 if (mutt_write_mime_body (t, f) == -1)
407 fprintf (f, "\n--%s--\n", boundary);
408 return (ferror (f) ? -1 : 0);
411 /* This is pretty gross, but it's the best solution for now... */
412 if ((WithCrypto & APPLICATION_PGP)
413 && a->type == TYPEAPPLICATION
414 && str_cmp (a->subtype, "pgp-encrypted") == 0) {
415 fputs ("Version: 1\n", f);
419 if ((fpin = fopen (a->filename, "r")) == NULL) {
420 debug_print (1, ("%s no longer exists!\n", a->filename));
421 mutt_error (_("%s no longer exists!"), a->filename);
425 if (a->type == TYPETEXT && (!a->noconv))
426 fc = fgetconv_open (fpin, a->file_charset,
427 mutt_get_body_charset (send_charset,
428 sizeof (send_charset), a), 0);
430 fc = fgetconv_open (fpin, 0, 0, 0);
432 if (a->encoding == ENCQUOTEDPRINTABLE)
433 encode_quoted (fc, f, write_as_text_part (a));
434 else if (a->encoding == ENCBASE64)
435 encode_base64 (fc, f, write_as_text_part (a));
436 else if (a->type == TYPETEXT && (!a->noconv))
437 encode_8bit (fc, f, write_as_text_part (a));
439 mutt_copy_stream (fpin, f);
441 fgetconv_close (&fc);
444 return (ferror (f) ? -1 : 0);
447 #undef write_as_text_part
449 #define BOUNDARYLEN 16
450 void mutt_generate_boundary (PARAMETER ** parm)
452 char rs[BOUNDARYLEN + 1];
457 for (i = 0; i < BOUNDARYLEN; i++)
458 *p++ = B64Chars[LRAND () % sizeof (B64Chars)];
461 mutt_set_parameter ("boundary", rs, parm);
473 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
477 int whitespace = s->whitespace;
479 int linelen = s->linelen;
480 int was_cr = s->was_cr;
482 if (!d) { /* This signals EOF */
485 if (linelen > info->linemax)
486 info->linemax = linelen;
491 for (; dlen; d++, dlen--) {
504 if (linelen > info->linemax)
505 info->linemax = linelen;
520 if (linelen > info->linemax)
521 info->linemax = linelen;
526 else if (ch == '\r') {
534 else if (ch == '\t' || ch == '\f') {
538 else if (ch < 32 || ch == 127)
542 if ((ch == 'F') || (ch == 'f'))
552 if (linelen == 2 && ch != 'r')
554 else if (linelen == 3 && ch != 'o')
556 else if (linelen == 4) {
569 if (ch != ' ' && ch != '\t')
574 s->whitespace = whitespace;
576 s->linelen = linelen;
581 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
582 #define BUGGY_ICONV 1
585 * Find the best charset conversion of the file from fromcode into one
586 * of the tocodes. If successful, set *tocode and CONTENT *info and
587 * return the number of characters converted inexactly. If no
588 * conversion was possible, return -1.
590 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
591 * which would otherwise prevent us from knowing the number of inexact
592 * conversions. Where the candidate target charset is UTF-8 we avoid
593 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
594 * fails with some libraries.
596 * We assume that the output from iconv is never more than 4 times as
597 * long as the input for any pair of charsets we might be interested
600 static size_t convert_file_to (FILE * file, const char *fromcode,
601 int ncodes, const char **tocodes,
602 int *tocode, CONTENT * info)
606 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
607 ICONV_CONST char *ib, *ub;
609 size_t ibl, obl, ubl, ubl1, n, ret;
612 CONTENT_STATE *states;
615 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
616 if (cd1 == (iconv_t) (-1))
619 cd = mem_calloc (ncodes, sizeof (iconv_t));
620 score = mem_calloc (ncodes, sizeof (size_t));
621 states = mem_calloc (ncodes, sizeof (CONTENT_STATE));
622 infos = mem_calloc (ncodes, sizeof (CONTENT));
624 for (i = 0; i < ncodes; i++)
625 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
626 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
628 /* Special case for conversion to UTF-8 */
629 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
635 /* Try to fill input buffer */
636 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
639 /* Convert to UTF-8 */
641 ob = bufu, obl = sizeof (bufu);
642 n = iconv (cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
643 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
644 if (n == (size_t) (-1) &&
645 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
646 assert (errno == EILSEQ ||
647 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
653 /* Convert from UTF-8 */
654 for (i = 0; i < ncodes; i++)
655 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
656 ub = bufu, ubl = ubl1;
657 ob = bufo, obl = sizeof (bufo);
658 n = iconv (cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
659 if (n == (size_t) (-1)) {
660 assert (errno == E2BIG ||
661 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
662 score[i] = (size_t) (-1);
666 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
669 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
670 /* Special case for conversion to UTF-8 */
671 update_content_info (&infos[i], &states[i], bufu, ubl1);
674 /* Save unused input */
675 memmove (bufi, ib, ibl);
676 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
683 /* Find best score */
685 for (i = 0; i < ncodes; i++) {
686 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
687 /* Special case for conversion to UTF-8 */
692 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
694 else if (ret == (size_t) (-1) || score[i] < ret) {
701 if (ret != (size_t) (-1)) {
702 memcpy (info, &infos[*tocode], sizeof (CONTENT));
703 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
707 for (i = 0; i < ncodes; i++)
708 if (cd[i] != (iconv_t) (-1))
720 #endif /* !HAVE_ICONV */
724 * Find the first of the fromcodes that gives a valid conversion and
725 * the best charset conversion of the file into one of the tocodes. If
726 * successful, set *fromcode and *tocode to dynamically allocated
727 * strings, set CONTENT *info, and return the number of characters
728 * converted inexactly. If no conversion was possible, return -1.
730 * Both fromcodes and tocodes may be colon-separated lists of charsets.
731 * However, if fromcode is zero then fromcodes is assumed to be the
732 * name of a single charset even if it contains a colon.
734 static size_t convert_file_from_to (FILE * file,
735 const char *fromcodes,
736 const char *tocodes, char **fromcode,
737 char **tocode, CONTENT * info)
745 /* Count the tocodes */
747 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
748 if ((c1 = strchr (c, ':')) == c)
754 tcode = mem_malloc (ncodes * sizeof (char *));
755 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
756 if ((c1 = strchr (c, ':')) == c)
758 tcode[i] = str_substrdup (c, c1);
763 /* Try each fromcode in turn */
764 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
765 if ((c1 = strchr (c, ':')) == c)
767 fcode = str_substrdup (c, c1);
769 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
771 if (ret != (size_t) (-1)) {
781 /* There is only one fromcode */
782 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
784 if (ret != (size_t) (-1)) {
791 for (i = 0; i < ncodes; i++)
792 mem_free (&tcode[i]);
800 * Analyze the contents of a file to determine which MIME encoding to use.
801 * Also set the body charset, sometimes, or not.
803 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
808 char *fromcode = NULL;
819 if (stat (fname, &sb) == -1) {
820 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
824 if (!S_ISREG (sb.st_mode)) {
825 mutt_error (_("%s isn't a regular file."), fname);
829 if ((fp = fopen (fname, "r")) == NULL) {
830 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
834 info = mem_calloc (1, sizeof (CONTENT));
835 memset (&state, 0, sizeof (state));
837 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
838 char *chs = mutt_get_parameter ("charset", b->parameter);
839 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
840 FileCharset : Charset) : Charset;
841 if (Charset && (chs || SendCharset) &&
842 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
843 &fromcode, &tocode, info) != (size_t) (-1)) {
845 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
846 mutt_set_parameter ("charset", chsbuf, &b->parameter);
848 b->file_charset = fromcode;
856 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
857 update_content_info (info, &state, buffer, r);
858 update_content_info (info, &state, 0, 0);
862 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
863 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
865 && !mutt_is_us_ascii (Charset) ? Charset :
866 "unknown-8bit"), &b->parameter);
871 /* Given a file with path ``s'', see if there is a registered MIME type.
872 * returns the major MIME type, and copies the subtype to ``d''. First look
873 * for ~/.mime.types, then look in a system mime.types if we can find one.
874 * The longest match is used so that we can match `ps.gz' when `gz' also
878 int mutt_lookup_mime_type (BODY * att, const char *path)
882 char buf[LONG_STRING];
883 char subtype[STRING], xtype[STRING];
885 int szf, sze, cur_sze;
893 szf = str_len (path);
895 for (count = 0; count < 4; count++) {
897 * can't use strtok() because we use it in an inner loop below, so use
898 * a switch statement here instead.
902 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
905 strfcpy (buf, SYSCONFDIR "/muttng-mime.types", sizeof (buf));
908 strfcpy (buf, PKGDATADIR "/mime.types", sizeof (buf));
911 strfcpy (buf, SYSCONFDIR "/mime.types", sizeof (buf));
914 debug_print (1, ("Internal error, count = %d.\n", count));
915 goto bye; /* shouldn't happen */
918 if ((f = fopen (buf, "r")) != NULL) {
919 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
920 /* weed out any comments */
921 if ((p = strchr (buf, '#')))
924 /* remove any leading space. */
928 /* position on the next field in this line */
929 if ((p = strpbrk (ct, " \t")) == NULL)
934 /* cycle through the file extensions */
935 while ((p = strtok (p, " \t\n"))) {
937 if ((sze > cur_sze) && (szf >= sze) &&
938 (str_casecmp (path + szf - sze, p) == 0
939 || ascii_strcasecmp (path + szf - sze, p) == 0) && (szf == sze
946 /* get the content-type */
948 if ((p = strchr (ct, '/')) == NULL) {
949 /* malformed line, just skip it. */
954 for (q = p; *q && !ISSPACE (*q); q++);
956 str_substrcpy (subtype, p, q, sizeof (subtype));
958 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
959 strfcpy (xtype, ct, sizeof (xtype));
972 if (type != TYPEOTHER || *xtype != '\0') {
974 str_replace (&att->subtype, subtype);
975 str_replace (&att->xtype, xtype);
981 void mutt_message_to_7bit (BODY * a, FILE * fp)
983 char temp[_POSIX_PATH_MAX];
989 if (!a->filename && fp)
991 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
992 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
997 if (stat (a->filename, &sb) == -1) {
998 mutt_perror ("stat");
1001 a->length = sb.st_size;
1005 if (!(fpout = safe_fopen (temp, "w+"))) {
1006 mutt_perror ("fopen");
1010 fseeko (fpin, a->offset, 0);
1011 a->parts = mutt_parse_messageRFC822 (fpin, a);
1013 transform_to_7bit (a->parts, fpin);
1015 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1016 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1018 fputs ("MIME-Version: 1.0\n", fpout);
1019 mutt_write_mime_header (a->parts, fpout);
1020 fputc ('\n', fpout);
1021 mutt_write_mime_body (a->parts, fpout);
1033 a->encoding = ENC7BIT;
1034 a->d_filename = a->filename;
1035 if (a->filename && a->unlink)
1036 unlink (a->filename);
1037 a->filename = str_dup (temp);
1039 if (stat (a->filename, &sb) == -1) {
1040 mutt_perror ("stat");
1043 a->length = sb.st_size;
1044 mutt_free_body (&a->parts);
1045 a->hdr->content = NULL;
1048 static void transform_to_7bit (BODY * a, FILE * fpin)
1050 char buff[_POSIX_PATH_MAX];
1054 memset (&s, 0, sizeof (s));
1055 for (; a; a = a->next) {
1056 if (a->type == TYPEMULTIPART) {
1057 if (a->encoding != ENC7BIT)
1058 a->encoding = ENC7BIT;
1060 transform_to_7bit (a->parts, fpin);
1062 else if (mutt_is_message_type (a->type, a->subtype)) {
1063 mutt_message_to_7bit (a, fpin);
1067 a->force_charset = 1;
1070 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1071 mutt_perror ("fopen");
1075 mutt_decode_attachment (a, &s);
1077 a->d_filename = a->filename;
1078 a->filename = str_dup (buff);
1080 if (stat (a->filename, &sb) == -1) {
1081 mutt_perror ("stat");
1084 a->length = sb.st_size;
1086 mutt_update_encoding (a);
1087 if (a->encoding == ENC8BIT)
1088 a->encoding = ENCQUOTEDPRINTABLE;
1089 else if (a->encoding == ENCBINARY)
1090 a->encoding = ENCBASE64;
1095 /* determine which Content-Transfer-Encoding to use */
1096 static void mutt_set_encoding (BODY * b, CONTENT * info)
1098 char send_charset[SHORT_STRING];
1100 if (b->type == TYPETEXT) {
1102 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1103 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1104 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1105 b->encoding = ENCQUOTEDPRINTABLE;
1106 else if (info->hibin)
1107 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1109 b->encoding = ENC7BIT;
1111 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1112 if (info->lobin || info->hibin) {
1113 if (option (OPTALLOW8BIT) && !info->lobin)
1114 b->encoding = ENC8BIT;
1116 mutt_message_to_7bit (b, NULL);
1119 b->encoding = ENC7BIT;
1121 else if (b->type == TYPEAPPLICATION
1122 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1123 b->encoding = ENC7BIT;
1126 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1127 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1130 /* Determine which encoding is smaller */
1131 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1132 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1133 b->encoding = ENCBASE64;
1135 b->encoding = ENCQUOTEDPRINTABLE;
1139 b->encoding = ENC7BIT;
1143 void mutt_stamp_attachment (BODY * a)
1145 a->stamp = time (NULL);
1148 /* Get a body's character set */
1150 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1154 if (b && b->type != TYPETEXT)
1158 p = mutt_get_parameter ("charset", b->parameter);
1161 mutt_canonical_charset (d, dlen, NONULL (p));
1163 strfcpy (d, "us-ascii", dlen);
1169 /* Assumes called from send mode where BODY->filename points to actual file */
1170 void mutt_update_encoding (BODY * a)
1173 char chsbuff[STRING];
1175 /* override noconv when it's us-ascii */
1176 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1179 if (!a->force_charset && !a->noconv)
1180 mutt_delete_parameter ("charset", &a->parameter);
1182 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1185 mutt_set_encoding (a, info);
1186 mutt_stamp_attachment (a);
1188 mem_free (&a->content);
1193 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1195 char buffer[LONG_STRING];
1198 int cmflags, chflags;
1199 int pgp = WithCrypto ? hdr->security : 0;
1202 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1203 (hdr->security & ENCRYPT)) {
1204 if (!crypt_valid_passphrase (hdr->security))
1209 mutt_mktemp (buffer);
1210 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1213 body = mutt_new_body ();
1214 body->type = TYPEMESSAGE;
1215 body->subtype = str_dup ("rfc822");
1216 body->filename = str_dup (buffer);
1219 body->disposition = DISPINLINE;
1222 mutt_parse_mime_message (ctx, hdr);
1227 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1228 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1229 chflags |= CH_MIME | CH_TXTPLAIN;
1230 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1231 if ((WithCrypto & APPLICATION_PGP))
1233 if ((WithCrypto & APPLICATION_SMIME))
1234 pgp &= ~SMIMEENCRYPT;
1236 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1237 if ((WithCrypto & APPLICATION_PGP)
1238 && mutt_is_multipart_encrypted (hdr->content)) {
1239 chflags |= CH_MIME | CH_NONEWLINE;
1240 cmflags = M_CM_DECODE_PGP;
1243 else if ((WithCrypto & APPLICATION_PGP)
1244 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1245 chflags |= CH_MIME | CH_TXTPLAIN;
1246 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1249 else if ((WithCrypto & APPLICATION_SMIME)
1250 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1251 chflags |= CH_MIME | CH_TXTPLAIN;
1252 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1253 pgp &= ~SMIMEENCRYPT;
1257 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1262 body->hdr = mutt_new_header ();
1263 body->hdr->offset = 0;
1264 /* we don't need the user headers here */
1265 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1267 body->hdr->security = pgp;
1268 mutt_update_encoding (body);
1269 body->parts = body->hdr->content;
1276 BODY *mutt_make_file_attach (const char *path)
1281 att = mutt_new_body ();
1282 att->filename = str_dup (path);
1284 /* Attempt to determine the appropriate content-type based on the filename
1291 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1292 path)) != TYPEOTHER || *xbuf != '\0') {
1294 att->subtype = str_dup (buf);
1295 att->xtype = str_dup (xbuf);
1300 mutt_lookup_mime_type (att, path);
1304 if ((info = mutt_get_content_info (path, att)) == NULL) {
1305 mutt_free_body (&att);
1309 if (!att->subtype) {
1310 if (info->lobin == 0
1311 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1313 * Statistically speaking, there should be more than 10% "lobin"
1314 * chars if this is really a binary file...
1316 att->type = TYPETEXT;
1317 att->subtype = str_dup ("plain");
1320 att->type = TYPEAPPLICATION;
1321 att->subtype = str_dup ("octet-stream");
1325 mutt_update_encoding (att);
1329 static int get_toplevel_encoding (BODY * a)
1333 for (; a; a = a->next) {
1334 if (a->encoding == ENCBINARY)
1336 else if (a->encoding == ENC8BIT)
1343 BODY *mutt_make_multipart (BODY * b)
1347 new = mutt_new_body ();
1348 new->type = TYPEMULTIPART;
1349 new->subtype = str_dup ("mixed");
1350 new->encoding = get_toplevel_encoding (b);
1351 mutt_generate_boundary (&new->parameter);
1353 new->disposition = DISPINLINE;
1359 /* remove the multipart body if it exists */
1360 BODY *mutt_remove_multipart (BODY * b)
1368 mutt_free_body (&t);
1373 char *mutt_make_date (char *s, size_t len)
1375 time_t t = time (NULL);
1376 struct tm *l = localtime (&t);
1377 time_t tz = mutt_local_tz (t);
1381 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1382 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1383 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1384 (int) tz / 60, (int) abs (tz) % 60);
1388 /* wrapper around mutt_write_address() so we can handle very large
1389 recipient lists without needing a huge temporary buffer in memory */
1390 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1394 char buf[LONG_STRING];
1402 rfc822_write_address (buf, sizeof (buf), adr, display);
1403 len = str_len (buf);
1404 if (count && linelen + len > 74) {
1406 linelen = len + 8; /* tab is usually about 8 spaces... */
1409 if (count && adr->mailbox) {
1417 if (!adr->group && adr->next && adr->next->mailbox) {
1427 /* arbitrary number of elements to grow the array by */
1432 /* need to write the list in reverse because they are stored in reverse order
1433 * when parsed to speed up threading
1435 void mutt_write_references (LIST * r, FILE * f)
1438 int refcnt = 0, refmax = 0;
1440 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1441 if (refcnt == refmax)
1442 mem_realloc (&ref, (refmax += REF_INC) * sizeof (LIST *));
1446 while (refcnt-- > 0) {
1448 fputs (ref[refcnt]->data, f);
1454 /* Note: all RFC2047 encoding should be done outside of this routine, except
1455 * for the "real name." This will allow this routine to be used more than
1456 * once, if necessary.
1458 * Likewise, all IDN processing should happen outside of this routine.
1460 * mode == 1 => "lite" mode (used for edit_hdrs)
1461 * mode == 0 => normal mode. write full header + MIME headers
1462 * mode == -1 => write just the envelope info (used for postponing messages)
1464 * privacy != 0 => will omit any headers which may identify the user.
1465 * Output generated is suitable for being sent through
1466 * anonymous remailer chains.
1470 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1471 int mode, int privacy)
1473 char buffer[LONG_STRING];
1475 LIST *tmp = env->userhdrs;
1476 int has_agent = 0; /* user defined user-agent header field exists */
1477 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1480 if (!option (OPTNEWSSEND))
1482 if (mode == 0 && !privacy)
1483 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1485 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1487 /* OPTUSEFROM is not consulted here so that we can still write a From:
1488 * field if the user sets it with the `my_hdr' command
1490 if (env->from && !privacy) {
1492 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1493 fprintf (fp, "From: %s\n", buffer);
1498 mutt_write_address_list (env->to, fp, 4, 0);
1502 if (!option (OPTNEWSSEND))
1504 if (EDIT_HEADER("To:"))
1505 fputs ("To:\n", fp);
1509 mutt_write_address_list (env->cc, fp, 4, 0);
1513 if (!option (OPTNEWSSEND))
1515 if (EDIT_HEADER("Cc:"))
1516 fputs ("Cc:\n", fp);
1519 if (mode != 0 || option (OPTWRITEBCC)) {
1520 fputs ("Bcc: ", fp);
1521 mutt_write_address_list (env->bcc, fp, 5, 0);
1526 if (!option (OPTNEWSSEND))
1528 if (EDIT_HEADER("Bcc:"))
1529 fputs ("Bcc:\n", fp);
1532 if (env->newsgroups)
1533 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1534 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1535 fputs ("Newsgroups:\n", fp);
1537 if (env->followup_to)
1538 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1539 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1540 fputs ("Followup-To:\n", fp);
1542 if (env->x_comment_to)
1543 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1544 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1545 EDIT_HEADER("X-Comment-To:"))
1546 fputs ("X-Comment-To:\n", fp);
1550 fprintf (fp, "Subject: %s\n", env->subject);
1551 else if (mode == 1 && EDIT_HEADER("Subject:"))
1552 fputs ("Subject:\n", fp);
1554 /* save message id if the user has set it */
1555 if (env->message_id && !privacy)
1556 fprintf (fp, "Message-ID: %s\n", env->message_id);
1558 if (env->reply_to) {
1559 fputs ("Reply-To: ", fp);
1560 mutt_write_address_list (env->reply_to, fp, 10, 0);
1562 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1563 fputs ("Reply-To:\n", fp);
1565 if (env->mail_followup_to)
1567 if (!option (OPTNEWSSEND))
1570 fputs ("Mail-Followup-To: ", fp);
1571 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1575 if (env->references) {
1576 fputs ("References:", fp);
1577 mutt_write_references (env->references, fp);
1581 /* Add the MIME headers */
1582 fputs ("MIME-Version: 1.0\n", fp);
1583 mutt_write_mime_header (attach, fp);
1586 if (env->in_reply_to) {
1587 fputs ("In-Reply-To:", fp);
1588 mutt_write_references (env->in_reply_to, fp);
1594 /* Add any user defined headers */
1595 for (; tmp; tmp = tmp->next) {
1596 if ((p = strchr (tmp->data, ':'))) {
1600 continue; /* don't emit empty fields. */
1602 /* check to see if the user has overridden the user-agent field */
1603 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1609 fputs (tmp->data, fp);
1614 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1618 if (OperatingSystem != NULL) {
1619 os = OperatingSystem;
1622 if (uname (&un) == -1) {
1629 /* Add a vanity header */
1630 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1633 list_del (&hdrs, (list_del_t*) _mem_free);
1635 return (ferror (fp) == 0 ? 0 : -1);
1638 static void encode_headers (LIST * h)
1644 for (; h; h = h->next) {
1645 if (!(p = strchr (h->data, ':')))
1656 rfc2047_encode_string (&tmp);
1657 mem_realloc (&h->data,
1658 str_len (h->data) + 2 + str_len (tmp) + 1);
1660 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1666 const char *mutt_fqdn (short may_hide_host)
1670 if (Fqdn && Fqdn[0] != '@') {
1673 if (may_hide_host && option (OPTHIDDENHOST)) {
1674 if ((p = strchr (Fqdn, '.')))
1677 /* sanity check: don't hide the host if
1678 * the fqdn is something like detebe.org.
1681 if (!p || !(q = strchr (p, '.')))
1689 static char mutt_normalized_char (char c)
1693 if (strchr (".!#$%&'*+-/=?^_`{|}~", c))
1695 return '.'; /* normalized character (we're stricter than RFC2822, 3.6.4) */
1698 static void mutt_gen_localpart (char *buf, unsigned int len, char *fmt)
1702 char tmp[SHORT_STRING];
1709 for (; *fmt; ++fmt) {
1715 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mday);
1716 str_ncat (buf, len, tmp, 2);
1719 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_hour);
1720 str_ncat (buf, len, tmp, 2);
1723 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mon + 1);
1724 str_ncat (buf, len, tmp, 2);
1727 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_min);
1728 str_ncat (buf, len, tmp, 2);
1731 snprintf (tmp, sizeof (tmp), "%lo", (unsigned long) now);
1732 str_ncat (buf, len, tmp, str_len (tmp));
1735 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) getpid ());
1736 str_ncat (buf, len, tmp, str_len (tmp));
1739 snprintf (tmp, sizeof (tmp), "%c", MsgIdPfx);
1740 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1741 str_ncat (buf, len, tmp, 1);
1744 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) rand ());
1745 str_ncat (buf, len, tmp, str_len (tmp));
1748 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) rand ());
1749 str_ncat (buf, len, tmp, str_len (tmp));
1752 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_sec);
1753 str_ncat (buf, len, tmp, 2);
1756 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) now);
1757 str_ncat (buf, len, tmp, str_len (tmp));
1760 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) now);
1761 str_ncat (buf, len, tmp, str_len (tmp));
1764 snprintf (tmp, sizeof (tmp), "%04d", tm->tm_year + 1900); /* this will break in the year 10000 ;-) */
1765 str_ncat (buf, len, tmp, 4);
1768 str_ncat (buf, len, "%", 1);
1771 str_ncat (buf, len, ".", 1); /* invalid formats are replaced by '.' */
1778 c = mutt_normalized_char (*fmt); /* @todo: filter out invalid characters */
1779 str_ncat (buf, len, &c, 1);
1784 char *mutt_gen_msgid (void)
1786 char buf[SHORT_STRING];
1787 char localpart[SHORT_STRING];
1788 unsigned int localpart_length;
1791 if (!(fqdn = mutt_fqdn (0)))
1792 fqdn = NONULL (Hostname);
1794 localpart_length = sizeof (buf) - str_len (fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1796 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1798 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1799 return (str_dup (buf));
1802 static RETSIGTYPE alarm_handler (int sig)
1807 /* invoke sendmail in a subshell
1808 path (in) path to program to execute
1809 args (in) arguments to pass to program
1810 msg (in) temp file containing message to send
1811 tempfile (out) if sendmail is put in the background, this points
1812 to the temporary file containing the stdout of the
1815 send_msg (const char *path, char **args, const char *msg, char **tempfile)
1821 mutt_block_signals_system ();
1824 /* we also don't want to be stopped right now */
1825 sigaddset (&set, SIGTSTP);
1826 sigprocmask (SIG_BLOCK, &set, NULL);
1828 if (SendmailWait >= 0) {
1829 char tmp[_POSIX_PATH_MAX];
1832 *tempfile = str_dup (tmp);
1835 if ((pid = fork ()) == 0) {
1836 struct sigaction act, oldalrm;
1838 /* save parent's ID before setsid() */
1841 /* we want the delivery to continue even after the main process dies,
1842 * so we put ourselves into another session right away
1846 /* next we close all open files */
1847 #if defined(OPEN_MAX)
1848 for (fd = 0; fd < OPEN_MAX; fd++)
1850 #elif defined(_POSIX_OPEN_MAX)
1851 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1859 /* now the second fork() */
1860 if ((pid = fork ()) == 0) {
1861 /* "msg" will be opened as stdin */
1862 if (open (msg, O_RDONLY, 0) < 0) {
1868 if (SendmailWait >= 0) {
1869 /* *tempfile will be opened as stdout */
1870 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1873 /* redirect stderr to *tempfile too */
1878 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1880 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1887 else if (pid == -1) {
1889 mem_free (tempfile);
1893 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1894 * SendmailWait = 0: wait forever
1895 * SendmailWait < 0: don't wait
1897 if (SendmailWait > 0) {
1899 act.sa_handler = alarm_handler;
1901 /* need to make sure waitpid() is interrupted on SIGALRM */
1902 act.sa_flags = SA_INTERRUPT;
1906 sigemptyset (&act.sa_mask);
1907 sigaction (SIGALRM, &act, &oldalrm);
1908 alarm (SendmailWait);
1910 else if (SendmailWait < 0)
1911 _exit (0xff & EX_OK);
1913 if (waitpid (pid, &st, 0) > 0) {
1914 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1915 if (SendmailWait && st == (0xff & EX_OK)) {
1916 unlink (*tempfile); /* no longer needed */
1917 mem_free (tempfile);
1921 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1922 if (SendmailWait > 0) {
1924 mem_free (tempfile);
1928 /* reset alarm; not really needed, but... */
1930 sigaction (SIGALRM, &oldalrm, NULL);
1932 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1933 /* the parent is already dead */
1935 mem_free (tempfile);
1941 sigprocmask (SIG_UNBLOCK, &set, NULL);
1943 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1944 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1946 st = S_ERR; /* error */
1948 mutt_unblock_signals_system (1);
1953 static char **add_args (char **args, size_t * argslen, size_t * argsmax,
1956 for (; addr; addr = addr->next) {
1957 /* weed out group mailboxes, since those are for display only */
1958 if (addr->mailbox && !addr->group) {
1959 if (*argslen == *argsmax)
1960 mem_realloc (&args, (*argsmax += 5) * sizeof (char *));
1961 args[(*argslen)++] = addr->mailbox;
1967 static char **add_option (char **args, size_t * argslen, size_t * argsmax,
1970 if (*argslen == *argsmax)
1971 mem_realloc (&args, (*argsmax += 5) * sizeof (char *));
1972 args[(*argslen)++] = s;
1976 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
1977 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
1978 const char *msg, /* file containing message */
1980 { /* message contains 8bit chars */
1981 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1983 size_t argslen = 0, argsmax = 0;
1987 if (option (OPTNEWSSEND)) {
1988 char cmd[LONG_STRING];
1990 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1993 i = nntp_post (msg);
2002 s = str_dup (Sendmail);
2006 while ((ps = strtok (ps, " "))) {
2007 if (argslen == argsmax)
2008 mem_realloc (&args, sizeof (char *) * (argsmax += 5));
2011 args[argslen++] = ps;
2013 path = str_dup (ps);
2014 ps = strrchr (ps, '/');
2019 args[argslen++] = ps;
2026 if (!option (OPTNEWSSEND)) {
2028 if (eightbit && option (OPTUSE8BITMIME))
2029 args = add_option (args, &argslen, &argsmax, "-B8BITMIME");
2031 if (option (OPTENVFROM)) {
2035 else if (from && !from->next)
2038 args = add_option (args, &argslen, &argsmax, "-f");
2039 args = add_args (args, &argslen, &argsmax, f);
2043 args = add_option (args, &argslen, &argsmax, "-N");
2044 args = add_option (args, &argslen, &argsmax, DsnNotify);
2047 args = add_option (args, &argslen, &argsmax, "-R");
2048 args = add_option (args, &argslen, &argsmax, DsnReturn);
2050 args = add_option (args, &argslen, &argsmax, "--");
2051 args = add_args (args, &argslen, &argsmax, to);
2052 args = add_args (args, &argslen, &argsmax, cc);
2053 args = add_args (args, &argslen, &argsmax, bcc);
2058 if (argslen == argsmax)
2059 mem_realloc (&args, sizeof (char *) * (++argsmax));
2061 args[argslen++] = NULL;
2063 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2065 const char *e = mutt_strsysexit (i);
2067 e = mutt_strsysexit (i);
2068 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2073 if (stat (childout, &st) == 0 && st.st_size > 0)
2074 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2082 mem_free (&childout);
2087 if (i == (EX_OK & 0xff))
2089 else if (i == S_BKG)
2096 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2097 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2098 const char *msg, /* file containing message */
2100 { /* message contains 8bit chars */
2103 if (!option (OPTNEWSSEND))
2106 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2109 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2112 /* appends string 'b' to string 'a', and returns the pointer to the new
2114 char *mutt_append_string (char *a, const char *b)
2116 size_t la = str_len (a);
2118 mem_realloc (&a, la + str_len (b) + 1);
2119 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2123 /* returns 1 if char `c' needs to be quoted to protect from shell
2124 interpretation when executing commands in a subshell */
2125 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2127 /* returns 1 if string `s' contains characters which could cause problems
2128 when used on a command line to execute a command */
2129 int mutt_needs_quote (const char *s)
2132 if (INVALID_CHAR (*s))
2139 /* Quote a string to prevent shell escapes when this string is used on the
2140 command line to send mail. */
2141 char *mutt_quote_string (const char *s)
2146 rlen = str_len (s) + 3;
2147 pr = r = (char *) mem_malloc (rlen);
2150 if (INVALID_CHAR (*s)) {
2153 mem_realloc (&r, ++rlen);
2164 /* For postponing (!final) do the necessary encodings only */
2165 void mutt_prepare_envelope (ENVELOPE * env, int final)
2167 char buffer[LONG_STRING];
2170 if (env->bcc && !(env->to || env->cc)) {
2171 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2172 * recipients if there is no To: or Cc: field, so attempt to suppress
2173 * it by using an empty To: field.
2175 env->to = rfc822_new_address ();
2177 env->to->next = rfc822_new_address ();
2180 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2183 env->to->mailbox = str_dup (buffer);
2186 mutt_set_followup_to (env);
2188 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2189 env->message_id = mutt_gen_msgid ();
2192 /* Take care of 8-bit => 7-bit conversion. */
2193 rfc2047_encode_adrlist (env->to, "To");
2194 rfc2047_encode_adrlist (env->cc, "Cc");
2195 rfc2047_encode_adrlist (env->bcc, "Bcc");
2196 rfc2047_encode_adrlist (env->from, "From");
2197 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2198 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2202 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2205 rfc2047_encode_string (&env->subject);
2207 encode_headers (env->userhdrs);
2210 void mutt_unprepare_envelope (ENVELOPE * env)
2214 for (item = env->userhdrs; item; item = item->next)
2215 rfc2047_decode (&item->data);
2217 rfc822_free_address (&env->mail_followup_to);
2219 /* back conversions */
2220 rfc2047_decode_adrlist (env->to);
2221 rfc2047_decode_adrlist (env->cc);
2222 rfc2047_decode_adrlist (env->bcc);
2223 rfc2047_decode_adrlist (env->from);
2224 rfc2047_decode_adrlist (env->reply_to);
2225 rfc2047_decode (&env->subject);
2228 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2229 const char *resent_from, ADDRESS * env_from)
2233 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2234 MESSAGE *msg = NULL;
2237 /* Try to bounce each message out, aborting if we get any failures. */
2238 for (i = 0; i < Context->msgcount; i++)
2239 if (Context->hdrs[i]->tagged)
2241 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2246 /* If we failed to open a message, return with error */
2247 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2253 mutt_mktemp (tempfile);
2254 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2255 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2257 if (!option (OPTBOUNCEDELIVERED))
2258 ch_flags |= CH_WEED_DELIVERED;
2260 fseeko (fp, h->offset, 0);
2261 fprintf (f, "Resent-From: %s", resent_from);
2262 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2263 if (MsgIdFormat && *MsgIdFormat)
2264 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2265 fputs ("Resent-To: ", f);
2266 mutt_write_address_list (to, f, 11, 0);
2267 mutt_copy_header (fp, h, f, ch_flags, NULL);
2269 mutt_copy_bytes (fp, f, h->content->length);
2272 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2273 h->content->encoding == ENC8BIT);
2277 mx_close_message (&msg);
2282 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2285 const char *fqdn = mutt_fqdn (1);
2286 char resent_from[STRING];
2290 resent_from[0] = '\0';
2291 from = mutt_default_from ();
2294 rfc822_qualify (from, fqdn);
2296 rfc2047_encode_adrlist (from, "Resent-From");
2297 if (mutt_addrlist_to_idna (from, &err)) {
2298 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2301 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2304 unset_option (OPTNEWSSEND);
2307 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2309 rfc822_free_address (&from);
2315 /* given a list of addresses, return a list of unique addresses */
2316 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2318 ADDRESS *top = addr;
2319 ADDRESS **last = ⊤
2324 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2325 if (tmp->mailbox && addr->mailbox &&
2326 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2333 debug_print (2, ("Removing %s\n", addr->mailbox));
2338 rfc822_free_address (&addr);
2351 static void set_noconv_flags (BODY * b, short flag)
2353 for (; b; b = b->next) {
2354 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2355 set_noconv_flags (b->parts, flag);
2356 else if (b->type == TYPETEXT && b->noconv) {
2358 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2360 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2365 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2366 int post, char *fcc)
2370 char tempfile[_POSIX_PATH_MAX];
2371 FILE *tempfp = NULL;
2375 set_noconv_flags (hdr->content, 1);
2377 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2378 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2382 /* We need to add a Content-Length field to avoid problems where a line in
2383 * the message body begins with "From "
2385 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2386 mutt_mktemp (tempfile);
2387 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2388 mutt_perror (tempfile);
2389 mx_close_mailbox (&f, NULL);
2394 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2395 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2396 mx_close_mailbox (&f, NULL);
2400 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2401 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2403 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2406 /* (postponment) if this was a reply of some sort, <msgid> contians the
2407 * Message-ID: of message replied to. Save it using a special X-Mutt-
2408 * header so it can be picked up if the message is recalled at a later
2409 * point in time. This will allow the message to be marked as replied if
2410 * the same mailbox is still open.
2413 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2415 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2416 * it can be picked up when the message is recalled
2419 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2420 fprintf (msg->fp, "Status: RO\n");
2424 /* (postponment) if the mail is to be signed or encrypted, save this info */
2425 if ((WithCrypto & APPLICATION_PGP)
2426 && post && (hdr->security & APPLICATION_PGP)) {
2427 fputs ("X-Mutt-PGP: ", msg->fp);
2428 if (hdr->security & ENCRYPT)
2429 fputc ('E', msg->fp);
2430 if (hdr->security & SIGN) {
2431 fputc ('S', msg->fp);
2432 if (PgpSignAs && *PgpSignAs)
2433 fprintf (msg->fp, "<%s>", PgpSignAs);
2435 if (hdr->security & INLINE)
2436 fputc ('I', msg->fp);
2437 fputc ('\n', msg->fp);
2440 /* (postponment) if the mail is to be signed or encrypted, save this info */
2441 if ((WithCrypto & APPLICATION_SMIME)
2442 && post && (hdr->security & APPLICATION_SMIME)) {
2443 fputs ("X-Mutt-SMIME: ", msg->fp);
2444 if (hdr->security & ENCRYPT) {
2445 fputc ('E', msg->fp);
2446 if (SmimeCryptAlg && *SmimeCryptAlg)
2447 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2449 if (hdr->security & SIGN) {
2450 fputc ('S', msg->fp);
2451 if (SmimeDefaultKey && *SmimeDefaultKey)
2452 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2454 if (hdr->security & INLINE)
2455 fputc ('I', msg->fp);
2456 fputc ('\n', msg->fp);
2460 /* (postponement) if the mail is to be sent through a mixmaster
2461 * chain, save that information
2464 if (post && hdr->chain && hdr->chain) {
2467 fputs ("X-Mutt-Mix:", msg->fp);
2468 for (p = hdr->chain; p; p = p->next)
2469 fprintf (msg->fp, " %s", (char *) p->data);
2471 fputc ('\n', msg->fp);
2476 char sasha[LONG_STRING];
2479 mutt_write_mime_body (hdr->content, tempfp);
2481 /* make sure the last line ends with a newline. Emacs doesn't ensure
2482 * this will happen, and it can cause problems parsing the mailbox
2485 fseeko (tempfp, -1, 2);
2486 if (fgetc (tempfp) != '\n') {
2487 fseeko (tempfp, 0, 2);
2488 fputc ('\n', tempfp);
2492 if (ferror (tempfp)) {
2493 debug_print (1, ("%s: write failed.\n", tempfile));
2496 mx_commit_message (msg, &f); /* XXX - really? */
2497 mx_close_message (&msg);
2498 mx_close_mailbox (&f, NULL);
2502 /* count the number of lines */
2504 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2506 fprintf (msg->fp, "Content-Length: " OFF_T_FMT "\n", ftello (tempfp));
2507 fprintf (msg->fp, "Lines: %d\n\n", lines);
2509 /* copy the body and clean up */
2511 r = mutt_copy_stream (tempfp, msg->fp);
2512 if (fclose (tempfp) != 0)
2514 /* if there was an error, leave the temp version */
2519 fputc ('\n', msg->fp); /* finish off the header */
2520 r = mutt_write_mime_body (hdr->content, msg->fp);
2523 if (mx_commit_message (msg, &f) != 0)
2525 mx_close_message (&msg);
2526 mx_close_mailbox (&f, NULL);
2529 set_noconv_flags (hdr->content, 0);