]> git.llucax.com Git - software/mutt-debian.git/blob - rfc3676.c
Move Mutt with NNTP support to mutt-nntp package
[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   int delsp;
47 } flowed_state_t;
48
49 static int get_quote_level (const char *line)
50 {
51   int quoted = 0;
52   char *p = (char *) line;
53
54   while (p && *p == '>')
55   {
56     quoted++;
57     p++;
58   }
59
60   return quoted;
61 }
62
63 static size_t print_indent (int ql, STATE *s, int sp)
64 {
65   int i;
66   size_t wid = 0;
67
68   if (s->prefix)
69   {
70     /* use given prefix only for format=fixed replies to format=flowed,
71      * for format=flowed replies to format=flowed, use '>' indentation
72      */
73     if (option (OPTTEXTFLOWED))
74       ql++;
75     else
76     {
77       state_puts (s->prefix, s);
78       wid = mutt_strwidth (s->prefix);
79       sp = 0;
80     }
81   }
82   for (i = 0; i < ql; i++)
83     state_putc ('>', s);
84   if (sp)
85     state_putc (' ', s);
86   return ql + sp + wid;
87 }
88
89 static void flush_par (STATE *s, flowed_state_t *fst)
90 {
91   if (fst->width > 0)
92   {
93     state_putc ('\n', s);
94     fst->width = 0;
95   }
96   fst->spaces = 0;
97 }
98
99 static int quote_width (STATE *s, int ql)
100 {
101   size_t width = (Wrap ? mutt_term_width (Wrap) : FLOWED_MAX) - 1;
102   if (s->flags & M_REPLYING && width > FLOWED_MAX)
103     width = FLOWED_MAX;
104   if (ql + 1 < width)
105     width -= ql + 1;
106   return width;
107 }
108
109 static void print_flowed_line (char *line, STATE *s, int ql,
110                                flowed_state_t *fst, int term)
111 {
112   size_t width, w, words = 0;
113   char *p;
114   char last;
115
116   if (!line || !*line)
117   {
118     /* flush current paragraph (if any) first */
119     flush_par (s, fst);
120     print_indent (ql, s, 0);
121     state_putc ('\n', s);
122     return;
123   }
124
125   width = quote_width (s, ql);
126   last = line[mutt_strlen (line) - 1];
127
128   dprint (4, (debugfile, "f=f: line [%s], width = %ld, spaces = %d\n",
129               NONULL(line), (long)width, fst->spaces));
130
131   for (p = (char *)line, words = 0; (p = strsep (&line, " ")) != NULL ; )
132   {
133     dprint(4,(debugfile,"f=f: word [%s], width: %d, remaining = [%s]\n",
134               p, fst->width, line));
135
136     /* remember number of spaces */
137     if (!*p)
138     {
139       dprint(4,(debugfile,"f=f: additional space\n"));
140       fst->spaces++;
141       continue;
142     }
143     /* there's exactly one space prior to every but the first word */
144     if (words)
145       fst->spaces++;
146
147     w = mutt_strwidth (p);
148     /* see if we need to break the line but make sure the first
149        word is put on the line regardless;
150        if for DelSp=yes only one trailing space is used, we probably
151        have a long word that we should break within (we leave that
152        up to the pager or user) */
153     if (!(!fst->spaces && fst->delsp && last != ' ') &&
154         w < width && w + fst->width + fst->spaces > width)
155     {
156       dprint(4,(debugfile,"f=f: break line at %d, %d spaces left\n",
157                 fst->width, fst->spaces));
158       /* only honor trailing spaces for format=flowed replies */
159       if (option(OPTTEXTFLOWED))
160         for ( ; fst->spaces; fst->spaces--)
161           state_putc (' ', s);
162       state_putc ('\n', s);
163       fst->width = 0;
164       fst->spaces = 0;
165       words = 0;
166     }
167
168     if (!words && !fst->width)
169       fst->width = print_indent (ql, s, !(s->flags & M_REPLYING) &&
170                                  (ql > 0 || s->prefix));
171     fst->width += w + fst->spaces;
172     for ( ; fst->spaces; fst->spaces--)
173       state_putc (' ', s);
174     state_puts (p, s);
175     words++;
176   }
177
178   if (term)
179     flush_par (s, fst);
180 }
181
182 static void print_fixed_line (const char *line, STATE *s, int ql,
183                               flowed_state_t *fst)
184 {
185   print_indent (ql, s, !(s->flags & M_REPLYING) && (ql > 0 || s->prefix));
186   if (line && *line)
187     state_puts (line, s);
188   state_putc ('\n', s);
189
190   fst->width = 0;
191   fst->spaces = 0;
192 }
193
194 int rfc3676_handler (BODY * a, STATE * s)
195 {
196   char *buf = NULL, *t = NULL;
197   unsigned int quotelevel = 0, newql = 0, sigsep = 0;
198   int buf_off = 0, delsp = 0, fixed = 0;
199   size_t buf_len = 0, sz = 0;
200   flowed_state_t fst;
201
202   memset (&fst, 0, sizeof (fst));
203
204   /* respect DelSp of RfC3676 only with f=f parts */
205   if ((t = (char *) mutt_get_parameter ("delsp", a->parameter)))
206   {
207     delsp = mutt_strlen (t) == 3 && ascii_strncasecmp (t, "yes", 3) == 0;
208     t = NULL;
209     fst.delsp = 1;
210   }
211
212   dprint (4, (debugfile, "f=f: DelSp: %s\n", delsp ? "yes" : "no"));
213
214   while ((buf = mutt_read_line (buf, &sz, s->fpin, NULL, 0)))
215   {
216     buf_len = mutt_strlen (buf);
217     newql = get_quote_level (buf);
218
219     /* end flowed paragraph (if we're within one) if quoting level
220      * changes (should not but can happen, see RFC 3676, sec. 4.5.)
221      */
222     if (newql != quotelevel)
223       flush_par (s, &fst);
224
225     quotelevel = newql;
226     buf_off = newql;
227
228     /* respect sender's space-stuffing by removing one leading space */
229     if (buf[buf_off] == ' ')
230       buf_off++;
231
232     /* test for signature separator */
233     sigsep = ascii_strcmp (buf + buf_off, "-- ") == 0;
234
235     /* a fixed line either has no trailing space or is the
236      * signature separator */
237     fixed = buf_len == buf_off || buf[buf_len - 1] != ' ' || sigsep;
238
239     /* print fixed-and-standalone, fixed-and-empty and sigsep lines as
240      * fixed lines */
241     if ((fixed && (!fst.width || !buf_len)) || sigsep)
242     {
243       /* if we're within a flowed paragraph, terminate it */
244       flush_par (s, &fst);
245       print_fixed_line (buf + buf_off, s, quotelevel, &fst);
246       continue;
247     }
248
249     /* for DelSp=yes, we need to strip one SP prior to CRLF on flowed lines */
250     if (delsp && !fixed)
251       buf[--buf_len] = '\0';
252
253     print_flowed_line (buf + buf_off, s, quotelevel, &fst, fixed);
254   }
255
256   flush_par (s, &fst);
257
258   FREE (&buf);
259   return (0);
260 }
261
262 /*
263  * This routine does RfC3676 space stuffing since it's a MUST.
264  * Space stuffing means that we have to add leading spaces to
265  * certain lines:
266  *   - lines starting with a space
267  *   - lines starting with 'From '
268  * This routine is only called once right after editing the
269  * initial message so it's up to the user to take care of stuffing
270  * when editing the message several times before actually sending it
271  *
272  * This is more or less a hack as it replaces the message's content with
273  * a freshly created copy in a tempfile and modifies the file's mtime
274  * so we don't trigger code paths watching for mtime changes
275  */
276 void rfc3676_space_stuff (HEADER* hdr)
277 {
278 #if DEBUG
279   int lc = 0;
280   size_t len = 0;
281   unsigned char c = '\0';
282 #endif
283   FILE *in = NULL, *out = NULL;
284   char buf[LONG_STRING];
285   char tmpfile[_POSIX_PATH_MAX];
286
287   if (!hdr || !hdr->content || !hdr->content->filename)
288     return;
289
290   dprint (2, (debugfile, "f=f: postprocess %s\n", hdr->content->filename));
291
292   if ((in = safe_fopen (hdr->content->filename, "r")) == NULL)
293     return;
294
295   mutt_mktemp (tmpfile, sizeof (tmpfile));
296   if ((out = safe_fopen (tmpfile, "w+")) == NULL)
297   {
298     safe_fclose (&in);
299     return;
300   }
301
302   while (fgets (buf, sizeof (buf), in))
303   {
304     if (ascii_strncmp ("From ", buf, 5) == 0 || buf[0] == ' ') {
305       fputc (' ', out);
306 #if DEBUG
307       lc++;
308       len = mutt_strlen (buf);
309       if (len > 0)
310       {
311         c = buf[len-1];
312         buf[len-1] = '\0';
313       }
314       dprint (4, (debugfile, "f=f: line %d needs space-stuffing: '%s'\n",
315                   lc, buf));
316       if (len > 0)
317         buf[len-1] = c;
318 #endif
319     }
320     fputs (buf, out);
321   }
322   safe_fclose (&in);
323   safe_fclose (&out);
324   mutt_set_mtime (hdr->content->filename, tmpfile);
325   unlink (hdr->content->filename);
326   mutt_str_replace (&hdr->content->filename, tmpfile);
327 }