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