handler.c hash.c hdrline.c headers.c help.c hook.c keymap.c \
main.c mbox.c menu.c mh.c mx.c pager.c parse.c pattern.c \
postpone.c query.c recvattach.c recvcmd.c \
- rfc822.c rfc1524.c rfc2047.c rfc2231.c \
+ rfc822.c rfc1524.c rfc2047.c rfc2231.c rfc3676.c \
score.c send.c sendlib.c signal.c sort.c \
status.c system.c thread.c charset.c history.c lib.c \
muttlib.c editmsg.c mbyte.c \
mailbox.h mapping.h md5.h mime.h mutt.h mutt_curses.h mutt_menu.h \
mutt_regex.h mutt_sasl.h mutt_socket.h mutt_ssl.h mutt_tunnel.h \
mx.h pager.h pgp.h pop.h protos.h rfc1524.h rfc2047.h \
- rfc2231.h rfc822.h sha1.h sort.h mime.types VERSION prepare \
+ rfc2231.h rfc822.h rfc3676.h sha1.h sort.h mime.types VERSION prepare \
_regex.h OPS.MIX README.SECURITY remailer.c remailer.h browser.h \
mbyte.h lib.h extlib.c pgpewrap.c smime_keys.pl pgplib.h Muttrc.head Muttrc \
makedoc.c stamp-doc-rc README.SSL smime.h\
+patch-1.5.14hg.muttng.ff.6
</sect2>
+<sect2>
+<title>Sending format=flowed messages</title>
+
+<sect3>
+<title>Concept</title>
+
+<para>
+<literal>format=flowed</literal>-style messages (or <literal>f=f</literal>
+for short) are plain ASCII messages that consist of paragraphs which a receiver's
+mail client may reformat to its own needs which mostly means to
+customize line lengths regardless of what the sender sent. Technically this is
+achieved by letting lines of a ``flowable'' paragraph end in spaces.
+</para>
+
+<para>
+While for text-mode clients like mutt it's the best way to assume only a
+standard 80x25 character cell terminal, it may be desired to let the
+receiver decide completely how to view a message.
+</para>
+
+</sect3>
+
+<sect3>
+<title>Mutt support</title>
+
+<para>
+Mutt only supports setting the required <literal>format=flowed</literal>
+MIME parameter on outgoing messages if the <link linkend="text-flowed"
+>$text_flowed</link> variable is set. It does not add the
+trailing spaces nor does it provide any other feature related to
+composing <literal>f=f</literal> messages (like reformatting
+non-<literal>f=f</literal> parts of a reply to <literal>f=f</literal>
+before calling the editor).
+</para>
+
+<para>
+After editing the initial message text and before entering
+the compose menu, mutt properly space-stuffes the message.
+<emphasis>Space-stuffing</emphasis> is required by RfC3676 defining
+<literal>format=flowed</literal> and means to prepend a space to all
+lines starting with a space and lines starting with the word
+<literal>From</literal> (followed by space). All leading spaces are to
+be removed by receiving clients to restore the original message.
+</para>
+
+</sect3>
+
+<sect3>
+<title>Editor considerations</title>
+
+<para>
+As mutt provides no additional features to compose <literal>f=f</literal>
+messages, it's completely up to the user and his editor to produce
+proper messages. Please consider your editor's documentation if you
+intend to send <literal>f=f</literal> messages.
+</para>
+
+<para>
+Please note that when editing messages from the compose menu several
+times before really sending a mail, it's up to the user to ensure that
+the message is properly space-stuffed.
+</para>
+
+<para>
+For example, <emphasis>vim</emphasis> provides the <literal>w</literal>
+flag for its <literal>formatoptions</literal> setting to assist in
+creating <literal>f=f</literal> messages, see <literal>:help
+fo-table</literal> for details.
+</para>
+
+</sect3>
+
+</sect2>
+
</sect1>
<sect1 id="forwarding-mail">
#include "copy.h"
#include "charset.h"
#include "mutt_crypt.h"
-
+#include "rfc3676.h"
#define BUFI_SIZE 1000
#define BUFO_SIZE 2000
return 0;
}
-/*
- * An implementation of RFC 2646.
- *
- * NOTE: This still has to be made UTF-8 aware.
- *
- */
-
-#define FLOWED_MAX 77
-
-static void flowed_quote (STATE *s, int level)
-{
- int i;
-
- if (s->prefix)
- {
- if (option (OPTTEXTFLOWED))
- level++;
- else
- state_puts (s->prefix, s);
- }
-
- for (i = 0; i < level; i++)
- state_putc ('>', s);
-}
-
-static int flowed_maybe_quoted (char *cont)
-{
- return regexec ((regex_t *) QuoteRegexp.rx, cont, 0, NULL, 0) == 0;
-}
-
-static void flowed_stuff (STATE *s, char *cont, int level)
-{
- if (!option (OPTTEXTFLOWED) && !(s->flags & M_DISPLAY))
- return;
-
- if (s->flags & M_DISPLAY)
- {
- /*
- * Hack: If we are in the beginning of the line and there is
- * some text on the line which looks like it's quoted, turn off
- * ANSI colors, so quote coloring doesn't affect this line.
- */
- if (*cont && !level && !mutt_strcmp (Pager, "builtin") && flowed_maybe_quoted (cont))
- state_puts ("\033[0m",s);
- }
- else if ((!(s->flags & M_PRINTING)) &&
- ((*cont == ' ') || (*cont == '>') || (!level && !mutt_strncmp (cont, "From ", 5))))
- state_putc (' ', s);
-}
-
-static char *flowed_skip_indent (char *prefix, char *cont)
-{
- for (; *cont == ' ' || *cont == '\t'; cont++)
- *prefix++ = *cont;
- *prefix = '\0';
- return cont;
-}
-
-static int flowed_visual_strlen (char *l, int i)
-{
- int j;
- for (j = 0; *l; l++)
- {
- if (*l == '\t')
- j += 8 - ((i + j) % 8);
- else
- j++;
- }
-
- return j;
-}
-
-static int text_plain_flowed_handler (BODY *a, STATE *s)
-{
- char line[LONG_STRING];
- char indent[LONG_STRING];
-
- int quoted = -1;
- int last_quoted;
- int full = 1;
- int last_full;
- int col = 0, tmpcol;
-
- int i_add = 0;
- int add = 0;
- int soft = 0;
- int l, rl;
-
- int flowed_max;
- int bytes = a->length;
- int actually_wrap = 0;
-
- char *cont = NULL;
- char *tail = NULL;
- char *lc = NULL;
- char *t;
-
- *indent = '\0';
-
- if (s->prefix)
- add = 1;
-
- if ((flowed_max = FLOWED_MAX) > mutt_term_width (0) - 3)
- flowed_max = mutt_term_width (0) - 3;
- if (flowed_max > mutt_term_width (Wrap))
- flowed_max = mutt_term_width (Wrap);
-
-
- while (bytes > 0 && fgets (line, sizeof (line), s->fpin))
- {
- bytes -= strlen (line);
- tail = NULL;
-
- last_full = full;
-
- /*
- * If the last line wasn't fully read, this is the
- * tail of some line.
- */
- actually_wrap = !last_full;
-
- if ((t = strrchr (line, '\r')) || (t = strrchr (line, '\n')))
- {
- *t = '\0';
- full = 1;
- }
- else if ((t = strrchr (line, ' ')) || (t = strrchr (line, '\t')))
- {
- /*
- * Bad: We have a line of more than LONG_STRING characters.
- * (Which SHOULD NOT happen, since lines SHOULD be <= 79
- * characters long.)
- *
- * Try to simulate a soft line break at a word boundary.
- * Handle the rest of the line next time.
- *
- * Give up when we have a single word which is longer than
- * LONG_STRING characters. It will just be split into parts,
- * with a hard line break in between.
- */
-
- full = 0;
- l = strlen (t + 1);
- t[0] = ' ';
- t[1] = '\0';
-
- if (l)
- {
- fseek (s->fpin, -l, SEEK_CUR);
- bytes += l;
- }
- }
- else
- full = 0;
-
- last_quoted = quoted;
-
- if (last_full)
- {
- /*
- * We are in the beginning of a new line. Determine quote level
- * and indentation prefix
- */
- for (quoted = 0; line[quoted] == '>'; quoted++)
- ;
-
- cont = line + quoted;
-
- /* undo space stuffing */
- if (*cont == ' ')
- cont++;
-
- /* If there is an indentation, record it. */
- cont = flowed_skip_indent (indent, cont);
- i_add = flowed_visual_strlen (indent, quoted + add);
- }
- else
- {
- /*
- * This is just the tail of some over-long line. Keep
- * indentation and quote levels. Don't unstuff.
- */
- cont = line;
- }
-
- /* If we have a change in quoting depth, wrap. */
-
- if (col && last_quoted != quoted && last_quoted >= 0)
- {
- state_putc ('\n', s);
- col = 0;
- }
-
- do
- {
- if (tail)
- cont = tail;
-
- SKIPWS (cont);
-
- tail = NULL;
- soft = 0;
-
- /* try to find a point for word wrapping */
-
- retry_wrap:
- l = flowed_visual_strlen (cont, quoted + i_add + add + col);
- rl = mutt_strlen (cont);
- if (quoted + i_add + add + col + l > flowed_max)
- {
- actually_wrap = 1;
-
- for (tmpcol = quoted + i_add + add + col, t = cont;
- *t && tmpcol < flowed_max; t++)
- {
- if (*t == ' ' || *t == '\t')
- tail = t;
- if (*t == '\t')
- tmpcol = (tmpcol & ~7) + 8;
- else
- tmpcol++;
- }
-
- if (tail)
- {
- *tail++ = '\0';
- soft = 2;
- }
- }
-
- /* We seem to be desperate. Get me a new line, and retry. */
- if (!tail && (quoted + add + col + i_add + l > flowed_max) && col)
- {
- state_putc ('\n', s);
- col = 0;
- goto retry_wrap;
- }
-
- /* Detect soft line breaks. */
- if (!soft && ascii_strcmp (cont, "-- "))
- {
- lc = strrchr (cont, ' ');
- if (lc && lc[1] == '\0')
- soft = 1;
- }
-
- /*
- * If we are in the beginning of an output line, do quoting
- * and stuffing.
- *
- * We have to temporarily assemble the line since display
- * stuffing (i.e., turning off quote coloring) may depend on
- * the line's actual content. You never know what people put
- * into their regular expressions.
- */
- if (!col)
- {
- char tmp[LONG_STRING];
- snprintf (tmp, sizeof (tmp), "%s%s", indent, cont);
-
- flowed_quote (s, quoted);
- flowed_stuff (s, tmp, quoted + add);
-
- state_puts (indent, s);
- }
-
- /* output the text */
- state_puts (cont, s);
- col += flowed_visual_strlen (cont, quoted + i_add + add + col);
-
- /* possibly indicate a soft line break */
- if (soft == 2)
- {
- state_putc (' ', s);
- col++;
- }
-
- /*
- * Wrap if this display line corresponds to a
- * text line. Don't wrap if we changed the line.
- */
- if (!soft || (!actually_wrap && full))
- {
- state_putc ('\n', s);
- col = 0;
- }
- }
- while (tail);
- }
-
- if (col)
- state_putc ('\n', s);
-
- return 0;
-}
-
-
-
-
-
-
#define TXTHTML 1
#define TXTPLAIN 2
#define TXTENRICHED 3
if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (b))
handler = crypt_pgp_application_pgp_handler;
else if (ascii_strcasecmp ("flowed", mutt_get_parameter ("format", b->parameter)) == 0)
- handler = text_plain_flowed_handler;
+ handler = rfc3676_handler;
else
plaintext = 1;
}
return mtime;
}
+/* sets mtime of 'to' to mtime of 'from' */
+void mutt_set_mtime (const char* from, const char* to)
+{
+ struct utimbuf utim;
+ struct stat st;
+
+ if (stat (from, &st) != -1)
+ {
+ utim.actime = st.st_mtime;
+ utim.modtime = st.st_mtime;
+ utime (to, &utim);
+ }
+}
+
const char *mutt_make_version (void)
{
static char vstring[STRING];
ENVELOPE *mutt_read_rfc822_header (FILE *, HEADER *, short, short);
HEADER *mutt_dup_header (HEADER *);
+void mutt_set_mtime (const char *from, const char *to);
time_t mutt_decrease_mtime (const char *, struct stat *);
time_t mutt_local_tz (time_t);
time_t mutt_mktime (struct tm *, int);
--- /dev/null
+/*
+ * Copyright (C) 2005 Andreas Krennmair <ak@synflood.at>
+ * Copyright (C) 2005 Peter J. Holzer <hjp@hjp.net>
+ * Copyright (C) 2005-7 Rocco Rutte <pdmef@gmx.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* This file was originally part of mutt-ng */
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include "mutt.h"
+#include "mutt_curses.h"
+#include "ascii.h"
+#include "lib.h"
+
+#define FLOWED_MAX 77
+
+static int get_quote_level (const char *line)
+{
+ int quoted = 0;
+ char *p = (char *) line;
+
+ while (p && *p == '>')
+ {
+ quoted++;
+ p++;
+ }
+
+ return quoted;
+}
+
+static void print_empty_line (int ql, STATE *s)
+{
+ int i;
+
+ if (!(s->flags & M_REPLYING))
+ {
+ if (s->prefix)
+ state_puts (s->prefix, s);
+ for (i = 0; i < ql; i++)
+ state_putc ('>', s);
+ if (!(s->flags & M_REPLYING))
+ state_putc (' ', s);
+ }
+ state_putc ('\n', s);
+}
+
+static void print_flowed_line (const char *line, STATE *s, int ql)
+{
+ int width;
+ char *pos, *oldpos;
+ int len = mutt_strlen (line);
+ int i;
+
+ width = (Wrap ? mutt_term_width (Wrap) : FLOWED_MAX) - ql - 1;
+
+ if (!(s->flags & M_REPLYING))
+ --width;
+ if (width < 0)
+ width = COLS;
+
+ if (len == 0)
+ {
+ print_empty_line (ql, s);
+ return;
+ }
+
+ pos = (char *) line + width;
+ oldpos = (char *) line;
+
+ for (; oldpos < line + len; pos += width)
+ {
+ /* only search for a new position when we're not over the end */
+ if (pos < line + len)
+ {
+ if (*pos == ' ')
+ {
+ dprint (4, (debugfile, "f=f: found space directly at width\n"));
+ *pos = '\0';
+ ++pos;
+ }
+ else
+ {
+ char *save = pos;
+ dprint (4, (debugfile, "f=f: need to search for space\n"));
+
+ while (pos >= oldpos && *pos != ' ')
+ --pos;
+
+ if (pos < oldpos)
+ {
+ dprint (4, (debugfile, "f=f: no space found while searching "
+ "to left; going right\n"));
+ pos = save;
+ while (pos < line + len && *pos && *pos != ' ')
+ ++pos;
+ dprint (4, (debugfile, "f=f: found space at pos %d\n", pos-line));
+ }
+ else
+ {
+ dprint (4, (debugfile, "f=f: found space while searching to left\n"));
+ }
+
+ *pos = '\0';
+ ++pos;
+ }
+ }
+ else
+ {
+ dprint (4, (debugfile, "f=f: line completely fits on screen\n"));
+ }
+
+ if (s->prefix)
+ state_puts (s->prefix, s);
+
+ for (i = 0; i < ql; ++i)
+ state_putc ('>', s);
+ if (!(s->flags & M_REPLYING) && (ql > 0 || s->prefix))
+ state_putc (' ', s);
+ state_puts (oldpos, s);
+
+ if (pos < line + len)
+ state_putc (' ', s);
+ state_putc ('\n', s);
+ oldpos = pos;
+ }
+}
+
+int rfc3676_handler (BODY * a, STATE * s)
+{
+ int bytes = a->length;
+ char buf[LONG_STRING];
+ char *curline = safe_malloc (STRING);
+ char *t = NULL;
+ unsigned int curline_len = 1, quotelevel = 0, newql = 0, sigsep = 0;
+ int buf_off, buf_len;
+ int delsp = 0, fixed = 0;
+
+ *curline = '\0';
+
+ /* respect DelSp of RfC3676 only with f=f parts */
+ if ((t = (char *) mutt_get_parameter ("delsp", a->parameter)))
+ {
+ delsp = mutt_strlen (t) == 3 && ascii_strncasecmp (t, "yes", 3) == 0;
+ t = NULL;
+ }
+
+ dprint (2, (debugfile, "f=f: DelSp: %s\n", delsp ? "yes" : "no"));
+
+ while (bytes > 0 && fgets (buf, sizeof (buf), s->fpin))
+ {
+
+ buf_len = mutt_strlen (buf);
+ bytes -= buf_len;
+
+ newql = get_quote_level (buf);
+
+ /* a change of quoting level in a paragraph - shouldn't happen,
+ * but has to be handled - see RFC 3676, sec. 4.5.
+ */
+ if (newql != quotelevel && curline && *curline)
+ {
+ print_flowed_line (curline, s, quotelevel);
+ *curline = '\0';
+ curline_len = 1;
+ }
+ quotelevel = newql;
+
+ /* XXX - If a line is longer than buf (shouldn't happen), it is split.
+ * This will almost always cause an unintended line break, and
+ * possibly a change in quoting level. But that's better than not
+ * displaying it at all.
+ */
+ if ((t = strrchr (buf, '\r')) || (t = strrchr (buf, '\n')))
+ {
+ *t = '\0';
+ buf_len = t - buf;
+ }
+
+ buf_off = newql;
+
+ /* respect sender's space-stuffing by removing one leading space */
+ if (buf[buf_off] == ' ')
+ buf_off++;
+
+ /* test for signature separator */
+ sigsep = ascii_strcmp (buf + buf_off, "-- ") == 0;
+
+ /* a fixed line either has no trailing space or is the
+ * signature separator */
+ fixed = buf_len == 0 || buf[buf_len - 1] != ' ' || sigsep;
+
+ /* for DelSp=yes, we need to strip one SP prior to CRLF;
+ * in case of the signature separator, leave the space */
+ if (delsp && !sigsep && buf_len >= 1 && buf[buf_len-1] == ' ')
+ buf[--buf_len] = '\0';
+
+ /* we're here when last space removed because of DelSp was
+ * the last space and there isn't more -> done */
+ if ((buf_len - buf_off) < 0)
+ {
+ print_flowed_line (curline, s, quotelevel);
+ *curline = '\0';
+ curline_len = 1;
+ continue;
+ }
+
+ /* signature separator also flushes the previous paragraph */
+ if (sigsep && curline && *curline)
+ {
+ print_flowed_line (curline, s, quotelevel);
+ *curline = '\0';
+ curline_len = 1;
+ }
+
+ /* append remaining contents without quotes, space-stuffed
+ * spaces and with 1 trailing space (0 or 1 for DelSp=yes) */
+ safe_realloc (&curline, curline_len + buf_len - buf_off);
+ strcpy (curline + curline_len - 1, buf + buf_off); /* __STRCPY_CHECKED__ */
+ curline_len += buf_len - buf_off;
+
+ /* if this was a fixed line, the paragraph is finished */
+ if (fixed)
+ {
+ print_flowed_line (curline, s, quotelevel);
+ *curline = '\0';
+ curline_len = 1;
+ }
+
+ }
+ FREE(&curline);
+ return (0);
+}
+
+/*
+ * This routine does RfC3676 space stuffing since it's a MUST.
+ * Space stuffing means that we have to add leading spaces to
+ * certain lines:
+ * - lines starting with a space
+ * - lines starting with 'From '
+ * This routine is only called once right after editing the
+ * initial message so it's up to the user to take care of stuffing
+ * when editing the message several times before actually sending it
+ *
+ * This is more or less a hack as it replaces the message's content with
+ * a freshly created copy in a tempfile and modifies the file's mtime
+ * so we don't trigger code paths watching for mtime changes
+ */
+void rfc3676_space_stuff (HEADER* hdr)
+{
+#if DEBUG
+ int lc = 0;
+ size_t len = 0;
+ unsigned char c = '\0';
+#endif
+ FILE *in = NULL, *out = NULL;
+ char buf[LONG_STRING];
+ char tmpfile[_POSIX_PATH_MAX];
+
+ if (!hdr || !hdr->content || !hdr->content->filename)
+ return;
+
+ dprint (2, (debugfile, "f=f: postprocess %s\n", hdr->content->filename));
+
+ if ((in = safe_fopen (hdr->content->filename, "r")) == NULL)
+ return;
+
+ mutt_mktemp (tmpfile);
+ if ((out = safe_fopen (tmpfile, "w+")) == NULL)
+ {
+ fclose (in);
+ return;
+ }
+
+ while (fgets (buf, sizeof (buf), in))
+ {
+ if (ascii_strncmp ("From ", buf, 5) == 0 || buf[0] == ' ') {
+ fputc (' ', out);
+#if DEBUG
+ lc++;
+ len = mutt_strlen (buf);
+ if (len > 0)
+ {
+ c = buf[len-1];
+ buf[len-1] = '\0';
+ }
+ dprint (4, (debugfile, "f=f: line %d needs space-stuffing: '%s'\n",
+ lc, buf));
+ if (len > 0)
+ buf[len-1] = c;
+#endif
+ }
+ fputs (buf, out);
+ }
+ fclose (in);
+ fclose (out);
+ mutt_set_mtime (hdr->content->filename, tmpfile);
+ unlink (hdr->content->filename);
+ mutt_str_replace (&hdr->content->filename, tmpfile);
+}
--- /dev/null
+/*
+ * Copyright (C) 2005 Andreas Krennmair <ak@synflood.at>
+ * Copyright (C) 2005 Peter J. Holzer <hjp@hjp.net>
+ * Copyright (C) 2005 Rocco Rutte <pdmef@gmx.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* This file was originally part of mutt-ng */
+
+#ifndef _MUTT_RFC3676_H
+#define _MUTT_RFC3676_H
+
+#include "mutt.h"
+
+/* body handler implementing RfC 3676 for format=flowed */
+int rfc3676_handler (BODY *a, STATE *s);
+
+/* this does the space-stuffing for RfC3676 style messages */
+void rfc3676_space_stuff (HEADER *hdr);
+
+#endif /* !_MUTT_RFC3676_H */
#include "mutt_crypt.h"
#include "mutt_idna.h"
#include "url.h"
+#include "rfc3676.h"
#include <ctype.h>
#include <stdlib.h>
mutt_perror (msg->content->filename);
}
+ if (option (OPTTEXTFLOWED))
+ rfc3676_space_stuff (msg);
+
mutt_message_hook (NULL, msg, M_SEND2HOOK);
}