Rocco Rutte:
[apps/madmutt.git] / rfc3676.c
1 /*
2  * Parts were written/modified by:
3  * Andreas Krennmair <ak@synflood.at>
4  * Peter J. Holzer <hjp@hjp.net>
5  * Rocco Rutte <pdmef@cs.tu-berlin.de>
6  *
7  * This file is part of mutt-ng, see http://www.muttng.org/.
8  * It's licensed under the GNU General Public License,
9  * please see the file GPL in the top level source directory.
10  */
11
12 #if HAVE_CONFIG_H
13 # include "config.h"
14 #endif
15
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
19 #include <ctype.h>
20 #include <sys/wait.h>
21 #include <sys/stat.h>
22
23 #include "mutt.h"
24 #include "mutt_curses.h"
25 #include "ascii.h"
26 #include "handler.h"
27 #include "state.h"
28 #include "lib.h"
29
30 #include "lib/mem.h"
31 #include "lib/intl.h"
32 #include "lib/str.h"
33 #include "lib/debug.h"
34
35 #define FLOWED_MAX 77
36
37 static int get_quote_level (char *line)
38 {
39   int quoted;
40
41   for (quoted = 0; line[quoted] == '>'; quoted++);
42   return quoted;
43 }
44
45 static void print_flowed_line (char *line, STATE * s,
46                                int ql, int delsp,
47                                int* spaces, int space_len) {
48   int width;
49   char *pos, *oldpos;
50   int len = str_len (line);
51   int i;
52
53   if (MaxLineLength > 0) {
54     width = MaxLineLength - WrapMargin - ql - 1;
55     if (option (OPTSTUFFQUOTED))
56       --width;
57     if (width < 0)
58       width = MaxLineLength;
59   }
60   else {
61     if (option (OPTMBOXPANE))
62       width = COLS - SidebarWidth - WrapMargin - ql - 1;
63     else
64       width = COLS - WrapMargin - ql - 1;
65
66     if (option (OPTSTUFFQUOTED))
67       --width;
68     if (width < 0)
69       width = COLS;
70   }
71
72   if (str_len (line) == 0) {
73     if (option (OPTQUOTEEMPTY)) {
74       if (s->prefix)
75         state_puts(s->prefix,s);
76       for (i=0;i<ql;++i) state_putc('>',s);
77       if (option(OPTSTUFFQUOTED))
78         state_putc(' ',s);
79     }
80     state_putc('\n',s);
81     return;
82   }
83
84   pos = line + width;
85   oldpos = line;
86
87   for (; oldpos < line + len; pos += width) {
88     /* only search a new position when we're not over
89      * the end of the string w/ pos */
90     if (pos < line + len) {
91       if (*pos == ' ') {
92         debug_print (4, ("f=f: found space directly at width\n"));
93         *pos = '\0';
94         ++pos;
95       }
96       else {
97         char *save = pos;
98         debug_print (4, ("f=f: need to search for space\n"));
99
100         while (pos >= oldpos && *pos != ' ') {
101           --pos;
102         }
103         if (pos < oldpos) {
104           debug_print (4, ("f=f: no space found while searching "
105                            "to left; going right\n"));
106           pos = save;
107           while (pos < line + len && *pos && *pos != ' ') {
108             ++pos;
109           }
110           debug_print (4, ("f=f: found space at pos %d\n", pos-line));
111         } else {
112           debug_print (4, ("f=f: found space while searching to left\n"));
113         }
114         *pos = '\0';
115         ++pos;
116       }
117     }
118     else {
119       debug_print (4, ("f=f: line completely fits on screen\n"));
120     }
121     if (s->prefix)
122       state_puts (s->prefix, s);
123
124     for (i = 0; i < ql; ++i)
125       state_putc ('>', s);
126     if (option (OPTSTUFFQUOTED) && (ql > 0 || s->prefix))
127       state_putc (' ', s);
128
129     if (delsp && spaces && space_len > 0) {
130       /* here, we need to character-wise step through the line
131        * to eliminate all spaces which were trailing due to DelSp */
132       for (i = 0; i < str_len (oldpos); i++) {
133         if (oldpos[i] == ' ' && spaces[&(oldpos[i])-line] != 0) {
134           debug_print (4, ("f=f: DelSp: spaces[%d] forces space removal\n",
135                            &(oldpos[i])-line));
136           continue;
137         }
138         /* print space at oldpos[i] if it was non-trailing */
139         state_putc (oldpos[i], s);
140       }
141     } else
142       /* for no DelSp, just do whole line as per usual */
143       state_puts (oldpos, s);
144     /* fprintf(stderr,"print_flowed_line: `%s'\n",oldpos); */
145     if (pos < line + len)
146       state_putc (' ', s);
147     state_putc ('\n', s);
148     oldpos = pos;
149   }
150 }
151
152 int rfc3676_handler (BODY * a, STATE * s) {
153   int bytes = a->length;
154   char buf[LONG_STRING];
155   char *curline = str_dup ("");
156   char *t = NULL;
157   unsigned int curline_len = 1, space_len = 1,
158                quotelevel = 0, newql = 0;
159   int buf_off, buf_len;
160   int delsp = 0;
161   int* spaces = NULL;
162
163   /* respect DelSP of RfC3676 only with f=f parts */
164   if ((t = (char*) mutt_get_parameter ("delsp", a->parameter))) {
165     delsp = str_len (t) == 3 && ascii_strncasecmp (t, "yes", 3) == 0;
166     t = NULL;
167   }
168
169   debug_print (2, ("f=f: DelSp: %s\n", delsp ? "yes" : "no"));
170
171   while (bytes > 0 && fgets (buf, sizeof (buf), s->fpin)) {
172     buf_len = str_len (buf);
173     bytes -= buf_len;
174
175     newql = get_quote_level (buf);
176
177     /* a change of quoting level in a paragraph - shouldn't happen, 
178      * but has to be handled - see RFC 3676, sec. 4.5.
179      */
180     if (newql != quotelevel && curline && *curline) {
181       print_flowed_line (curline, s, quotelevel, delsp, spaces, space_len);
182       *curline = '\0';
183       curline_len = 1;
184       space_len = 0;
185     }
186     quotelevel = newql;
187
188     /* XXX - If a line is longer than buf (shouldn't happen), it is split.
189      * This will almost always cause an unintended line break, and 
190      * possibly a change in quoting level. But that's better than not
191      * displaying it at all.
192      */
193     if ((t = strrchr (buf, '\n')) || (t = strrchr (buf, '\r'))) {
194       *t = '\0';
195       buf_len = t - buf;
196     }
197     buf_off = newql;
198     /* respect space-stuffing */
199     if (buf[buf_off] == ' ')
200       buf_off++;
201
202     /* signature separator also flushes the previous paragraph */
203     if (strcmp(buf + buf_off, "-- ") == 0 && curline && *curline) {
204       print_flowed_line (curline, s, quotelevel, delsp, spaces, space_len);
205       *curline = '\0';
206       curline_len = 1;
207       space_len = 0;
208     }
209
210     mem_realloc (&curline, curline_len + buf_len - buf_off);
211     mem_realloc (&spaces, (curline_len + buf_len - buf_off)*sizeof (int));
212     strcpy (curline + curline_len - 1, buf + buf_off);
213     memset (&spaces[space_len], 0, (buf_len - buf_off)*sizeof (int));
214     curline_len += buf_len - buf_off;
215     space_len += buf_len - buf_off;
216
217     /* if this was a fixed line the paragraph is finished */
218     if (buf_len == 0 || buf[buf_len - 1] != ' ' || strcmp(buf + buf_off, "-- ") == 0) {
219       print_flowed_line (curline, s, quotelevel, delsp, spaces, space_len);
220       *curline = '\0';
221       curline_len = 1;
222       space_len = 0;
223     } else {
224       /* if last line we appended had a space and we have DelSp=yes,
225        * get a 1 into spaces array at proper position so that
226        * print_flowed_line() can handle it; don't kill the space
227        * right here 'cause we maybe need soft linebreaks to search for break
228        */
229       if (delsp && curline && *curline && curline_len-2 >= 0 &&
230           curline[curline_len-2] == ' ') {
231         debug_print (4, ("f=f: DelSp: marking spaces[%d] for later removal\n",
232                          curline_len-2));
233         spaces[curline_len-2] = 1;
234       }
235     }
236
237   }
238   mem_free (&spaces);
239   mem_free (&curline);
240   return (0);
241 }
242
243 void rfc3676_quote_line (STATE* s, char* dst, size_t dstlen,
244                          const char* line) {
245   char quote[SHORT_STRING];
246   int offset = 0, i = 0, count = 0;
247   regmatch_t pmatch[1];
248
249   quote[0] = '\0';
250
251   while (regexec ((regex_t *) QuoteRegexp.rx, &line[offset],
252                   1, pmatch, 0) == 0)
253     offset += pmatch->rm_eo;
254
255   if (offset > 0) {
256     /* first count number of real quoting characters;
257      * read: non-spaces
258      * this maybe just plain wrong, but leaving spaces
259      * within quoting characters is what I consider
260      * more plain wrong...
261      */
262     for (i = 0; i < offset; i++)
263       if (line[i] != ' ')
264         count++;
265     /* just make sure we're inside quote althoug we
266      * likely won't have more than SHORT_STRING quote levels... */
267     i = (count > SHORT_STRING-1) ? SHORT_STRING-1 : count;
268     memset (quote, '>', i);
269     quote[i] = '\0';
270   }
271   debug_print (4, ("f=f: quotelevel = %d, new prefix = '%s'\n",
272                    i, NONULL (quote)));
273   /* if we changed prefix, make sure we respect $stuff_quoted */
274   snprintf (dst, dstlen, "%s%s%s%s", NONULL (s->prefix), NONULL (quote),
275             option (OPTSTUFFQUOTED) && line[offset] != ' ' ? " " : "",
276             &line[offset]);
277 }