mutt stable branch with some hacks
1/*
2 * Copyright (C) 2005 Andreas Krennmair <ak@synflood.at>
3 * Copyright (C) 2005 Peter J. Holzer <hjp@hjp.net>
4 * Copyright (C) 2005-2009 Rocco Rutte <pdmef@gmx.net>
5 * Copyright (C) 2010 Michael R. Elkins <me@mutt.org>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23/* This file was originally part of mutt-ng */
24
25#if HAVE_CONFIG_H
26# include "config.h"
27#endif
28
29#include <stdlib.h>
30#include <string.h>
31#include <unistd.h>
32#include <ctype.h>
33#include <sys/wait.h>
34#include <sys/stat.h>
35
36#include "mutt.h"
37#include "mutt_curses.h"
38#include "ascii.h"
39#include "lib.h"
40
41#define FLOWED_MAX 72
42
43typedef struct flowed_state
44{
45 size_t width;
46 size_t spaces;
47 int delsp;
48} flowed_state_t;
49
50static int get_quote_level (const char *line)
51{
52 int quoted = 0;
53 char *p = (char *) line;
54
55 while (p && *p == '>')
56 {
57 quoted++;
58 p++;
59 }
60
61 return quoted;
62}
63
64/* Determines whether to add spacing between/after each quote level:
65 * >>>foo
66 * becomes
67 * > > > foo
68 */
69static int space_quotes (STATE *s)
70{
71 /* Allow quote spacing in the pager even for OPTTEXTFLOWED,
72 * but obviously not when replying.
73 */
74 if (option (OPTTEXTFLOWED) && (s->flags & MUTT_REPLYING))
75 return 0;
76
77 return option (OPTREFLOWSPACEQUOTES);
78}
79
80/* Determines whether to add a trailing space to quotes:
81 * >>> foo
82 * as opposed to
83 * >>>foo
84 */
85static int add_quote_suffix (STATE *s, int ql)
86{
87 if (s->flags & MUTT_REPLYING)
88 return 0;
89
90 if (space_quotes (s))
91 return 0;
92
93 if (!ql && !s->prefix)
94 return 0;
95
96 /* The prefix will add its own space */
97 if (!option (OPTTEXTFLOWED) && !ql && s->prefix)
98 return 0;
99
100 return 1;
101}
102
103static size_t print_indent (int ql, STATE *s, int add_suffix)
104{
105 int i;
106 size_t wid = 0;
107
108 if (s->prefix)
109 {
110 /* use given prefix only for format=fixed replies to format=flowed,
111 * for format=flowed replies to format=flowed, use '>' indentation
112 */
113 if (option (OPTTEXTFLOWED))
114 ql++;
115 else
116 {
117 state_puts (s->prefix, s);
118 wid = mutt_strwidth (s->prefix);
119 }
120 }
121 for (i = 0; i < ql; i++)
122 {
123 state_putc ('>', s);
124 if (space_quotes (s) )
125 state_putc (' ', s);
126 }
127 if (add_suffix)
128 state_putc (' ', s);
129
130 if (space_quotes (s))
131 ql *= 2;
132
133 return ql + add_suffix + wid;
134}
135
136static void flush_par (STATE *s, flowed_state_t *fst)
137{
138 if (fst->width > 0)
139 {
140 state_putc ('\n', s);
141 fst->width = 0;
142 }
143 fst->spaces = 0;
144}
145
146/* Calculate the paragraph width based upon the current quote level. The start
147 * of a quoted line will be ">>> ", so we need to subtract the space required
148 * for the prefix from the terminal width. */
149static int quote_width (STATE *s, int ql)
150{
151 int width = mutt_window_wrap_cols (MuttIndexWindow, ReflowWrap);
152 if (option(OPTTEXTFLOWED) && (s->flags & MUTT_REPLYING))
153 {
154 /* When replying, force a wrap at FLOWED_MAX to comply with RFC3676
155 * guidelines */
156 if (width > FLOWED_MAX)
157 width = FLOWED_MAX;
158 ++ql; /* When replying, we will add an additional quote level */
159 }
160 /* adjust the paragraph width subtracting the number of prefix chars */
161 width -= space_quotes (s) ? ql*2 : ql;
162 /* When displaying (not replying), there may be a space between the prefix
163 * string and the paragraph */
164 if (add_quote_suffix (s, ql))
165 --width;
166 /* failsafe for really long quotes */
167 if (width <= 0)
168 width = FLOWED_MAX; /* arbitrary, since the line will wrap */
169 return width;
170}
171
172static void print_flowed_line (char *line, STATE *s, int ql,
173 flowed_state_t *fst, int term)
174{
175 size_t width, w, words = 0;
176 char *p;
177 char last;
178
179 if (!line || !*line)
180 {
181 /* flush current paragraph (if any) first */
182 flush_par (s, fst);
183 print_indent (ql, s, 0);
184 state_putc ('\n', s);
185 return;
186 }
187
188 width = quote_width (s, ql);
189 last = line[mutt_strlen (line) - 1];
190
191 dprint (4, (debugfile, "f=f: line [%s], width = %ld, spaces = %d\n",
192 NONULL(line), (long)width, fst->spaces));
193
194 for (p = (char *)line, words = 0; (p = strsep (&line, " ")) != NULL ; )
195 {
196 dprint(4,(debugfile,"f=f: word [%s], width: %d, remaining = [%s]\n",
197 p, fst->width, line));
198
199 /* remember number of spaces */
200 if (!*p)
201 {
202 dprint(4,(debugfile,"f=f: additional space\n"));
203 fst->spaces++;
204 continue;
205 }
206 /* there's exactly one space prior to every but the first word */
207 if (words)
208 fst->spaces++;
209
210 w = mutt_strwidth (p);
211 /* see if we need to break the line but make sure the first
212 word is put on the line regardless;
213 if for DelSp=yes only one trailing space is used, we probably
214 have a long word that we should break within (we leave that
215 up to the pager or user) */
216 if (!(!fst->spaces && fst->delsp && last != ' ') &&
217 w < width && w + fst->width + fst->spaces > width)
218 {
219 dprint(4,(debugfile,"f=f: break line at %d, %d spaces left\n",
220 fst->width, fst->spaces));
221 /* only honor trailing spaces for format=flowed replies */
222 if (option(OPTTEXTFLOWED))
223 for ( ; fst->spaces; fst->spaces--)
224 state_putc (' ', s);
225 state_putc ('\n', s);
226 fst->width = 0;
227 fst->spaces = 0;
228 words = 0;
229 }
230
231 if (!words && !fst->width)
232 fst->width = print_indent (ql, s, add_quote_suffix (s, ql));
233 fst->width += w + fst->spaces;
234 for ( ; fst->spaces; fst->spaces--)
235 state_putc (' ', s);
236 state_puts (p, s);
237 words++;
238 }
239
240 if (term)
241 flush_par (s, fst);
242}
243
244static void print_fixed_line (const char *line, STATE *s, int ql,
245 flowed_state_t *fst)
246{
247 print_indent (ql, s, add_quote_suffix (s, ql));
248 if (line && *line)
249 state_puts (line, s);
250 state_putc ('\n', s);
251
252 fst->width = 0;
253 fst->spaces = 0;
254}
255
256int rfc3676_handler (BODY * a, STATE * s)
257{
258 char *buf = NULL, *t = NULL;
259 unsigned int quotelevel = 0, newql = 0, sigsep = 0;
260 int buf_off = 0, delsp = 0, fixed = 0;
261 size_t buf_len = 0, sz = 0;
262 flowed_state_t fst;
263
264 memset (&fst, 0, sizeof (fst));
265
266 /* respect DelSp of RfC3676 only with f=f parts */
267 if ((t = (char *) mutt_get_parameter ("delsp", a->parameter)))
268 {
269 delsp = mutt_strlen (t) == 3 && ascii_strncasecmp (t, "yes", 3) == 0;
270 t = NULL;
271 fst.delsp = 1;
272 }
273
274 dprint (4, (debugfile, "f=f: DelSp: %s\n", delsp ? "yes" : "no"));
275
276 while ((buf = mutt_read_line (buf, &sz, s->fpin, NULL, 0)))
277 {
278 buf_len = mutt_strlen (buf);
279 newql = get_quote_level (buf);
280
281 /* end flowed paragraph (if we're within one) if quoting level
282 * changes (should not but can happen, see RFC 3676, sec. 4.5.)
283 */
284 if (newql != quotelevel)
285 flush_par (s, &fst);
286
287 quotelevel = newql;
288 buf_off = newql;
289
290 /* respect sender's space-stuffing by removing one leading space */
291 if (buf[buf_off] == ' ')
292 buf_off++;
293
294 /* test for signature separator */
295 sigsep = ascii_strcmp (buf + buf_off, "-- ") == 0;
296
297 /* a fixed line either has no trailing space or is the
298 * signature separator */
299 fixed = buf_len == buf_off || buf[buf_len - 1] != ' ' || sigsep;
300
301 /* print fixed-and-standalone, fixed-and-empty and sigsep lines as
302 * fixed lines */
303 if ((fixed && (!fst.width || !buf_len)) || sigsep)
304 {
305 /* if we're within a flowed paragraph, terminate it */
306 flush_par (s, &fst);
307 print_fixed_line (buf + buf_off, s, quotelevel, &fst);
308 continue;
309 }
310
311 /* for DelSp=yes, we need to strip one SP prior to CRLF on flowed lines */
312 if (delsp && !fixed)
313 buf[--buf_len] = '\0';
314
315 print_flowed_line (buf + buf_off, s, quotelevel, &fst, fixed);
316 }
317
318 flush_par (s, &fst);
319
320 FREE (&buf);
321 return (0);
322}
323
324/*
325 * This routine does RfC3676 space stuffing since it's a MUST.
326 * Space stuffing means that we have to add leading spaces to
327 * certain lines:
328 * - lines starting with a space
329 * - lines starting with 'From '
330 * This routine is only called once right after editing the
331 * initial message so it's up to the user to take care of stuffing
332 * when editing the message several times before actually sending it
333 *
334 * This is more or less a hack as it replaces the message's content with
335 * a freshly created copy in a tempfile and modifies the file's mtime
336 * so we don't trigger code paths watching for mtime changes
337 */
338void rfc3676_space_stuff (HEADER* hdr)
339{
340#if DEBUG
341 int lc = 0;
342 size_t len = 0;
343 unsigned char c = '\0';
344#endif
345 FILE *in = NULL, *out = NULL;
346 char buf[LONG_STRING];
347 char tmpfile[_POSIX_PATH_MAX];
348
349 if (!hdr || !hdr->content || !hdr->content->filename)
350 return;
351
352 dprint (2, (debugfile, "f=f: postprocess %s\n", hdr->content->filename));
353
354 if ((in = safe_fopen (hdr->content->filename, "r")) == NULL)
355 return;
356
357 mutt_mktemp (tmpfile, sizeof (tmpfile));
358 if ((out = safe_fopen (tmpfile, "w+")) == NULL)
359 {
360 safe_fclose (&in);
361 return;
362 }
363
364 while (fgets (buf, sizeof (buf), in))
365 {
366 if (ascii_strncmp ("From ", buf, 5) == 0 || buf[0] == ' ') {
367 fputc (' ', out);
368#if DEBUG
369 lc++;
370 len = mutt_strlen (buf);
371 if (len > 0)
372 {
373 c = buf[len-1];
374 buf[len-1] = '\0';
375 }
376 dprint (4, (debugfile, "f=f: line %d needs space-stuffing: '%s'\n",
377 lc, buf));
378 if (len > 0)
379 buf[len-1] = c;
380#endif
381 }
382 fputs (buf, out);
383 }
384 safe_fclose (&in);
385 safe_fclose (&out);
386 mutt_set_mtime (hdr->content->filename, tmpfile);
387 unlink (hdr->content->filename);
388 mutt_str_replace (&hdr->content->filename, tmpfile);
389}