From: Rocco Rutte Date: Tue, 6 Mar 2007 17:49:48 +0000 (-0800) Subject: New format=flowed handler. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0fd382db380560d4b98f1733fb1c9a8e72c6563a;p=neomutt New format=flowed handler. --- diff --git a/Makefile.am b/Makefile.am index 23912e4d8..d299d028f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,7 +25,7 @@ mutt_SOURCES = $(BUILT_SOURCES) \ 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 \ @@ -72,7 +72,7 @@ EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP OPS.CRYPT OPS.SMIME TODO UPDATING \ 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\ diff --git a/PATCHES b/PATCHES index e69de29bb..d9386c44d 100644 --- a/PATCHES +++ b/PATCHES @@ -0,0 +1 @@ +patch-1.5.14hg.muttng.ff.6 diff --git a/doc/manual.xml.head b/doc/manual.xml.head index e4d0e117f..cba32e483 100644 --- a/doc/manual.xml.head +++ b/doc/manual.xml.head @@ -1204,6 +1204,80 @@ have a look at the mixmaster documentation. + +Sending format=flowed messages + + +Concept + + +format=flowed-style messages (or f=f +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. + + + +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. + + + + + +Mutt support + + +Mutt only supports setting the required format=flowed +MIME parameter on outgoing messages if the $text_flowed variable is set. It does not add the +trailing spaces nor does it provide any other feature related to +composing f=f messages (like reformatting +non-f=f parts of a reply to f=f +before calling the editor). + + + +After editing the initial message text and before entering +the compose menu, mutt properly space-stuffes the message. +Space-stuffing is required by RfC3676 defining +format=flowed and means to prepend a space to all +lines starting with a space and lines starting with the word +From (followed by space). All leading spaces are to +be removed by receiving clients to restore the original message. + + + + + +Editor considerations + + +As mutt provides no additional features to compose f=f +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 f=f messages. + + + +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. + + + +For example, vim provides the w +flag for its formatoptions setting to assist in +creating f=f messages, see :help +fo-table for details. + + + + + + diff --git a/handler.c b/handler.c index 6d7048b5e..8a53e7353 100644 --- a/handler.c +++ b/handler.c @@ -35,7 +35,7 @@ #include "copy.h" #include "charset.h" #include "mutt_crypt.h" - +#include "rfc3676.h" #define BUFI_SIZE 1000 #define BUFO_SIZE 2000 @@ -892,307 +892,6 @@ int text_enriched_handler (BODY *a, STATE *s) 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 @@ -1811,7 +1510,7 @@ int mutt_body_handler (BODY *b, STATE *s) 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; } diff --git a/muttlib.c b/muttlib.c index babe55adb..8b061116d 100644 --- a/muttlib.c +++ b/muttlib.c @@ -1519,6 +1519,20 @@ time_t mutt_decrease_mtime (const char *f, struct stat *st) 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]; diff --git a/protos.h b/protos.h index b74041ba1..20d313dd6 100644 --- a/protos.h +++ b/protos.h @@ -109,6 +109,7 @@ char *mutt_read_rfc822_line (FILE *, char *, size_t *); 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); diff --git a/rfc3676.c b/rfc3676.c new file mode 100644 index 000000000..6a6a7da34 --- /dev/null +++ b/rfc3676.c @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2005 Andreas Krennmair + * Copyright (C) 2005 Peter J. Holzer + * Copyright (C) 2005-7 Rocco Rutte + * + * 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 +#include +#include +#include +#include +#include + +#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); +} diff --git a/rfc3676.h b/rfc3676.h new file mode 100644 index 000000000..0cd50e47e --- /dev/null +++ b/rfc3676.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005 Andreas Krennmair + * Copyright (C) 2005 Peter J. Holzer + * Copyright (C) 2005 Rocco Rutte + * + * 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 */ diff --git a/send.c b/send.c index 0b4b8eb95..8690a86fd 100644 --- a/send.c +++ b/send.c @@ -31,6 +31,7 @@ #include "mutt_crypt.h" #include "mutt_idna.h" #include "url.h" +#include "rfc3676.h" #include #include @@ -1438,6 +1439,9 @@ ci_send_message (int flags, /* send mode */ mutt_perror (msg->content->filename); } + if (option (OPTTEXTFLOWED)) + rfc3676_space_stuff (msg); + mutt_message_hook (NULL, msg, M_SEND2HOOK); }