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 (!(s->flags & M_REPLYING) && 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 (!(s->flags & M_REPLYING) && 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 (!(s->flags & M_REPLYING) && 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 (!(s->flags & M_REPLYING) && option (OPTSTUFFQUOTED) &&
127         (ql > 0 || s->prefix))
128       state_putc (' ', s);
129
130     if (delsp && spaces && space_len > 0) {
131       /* here, we need to character-wise step through the line
132        * to eliminate all spaces which were trailing due to DelSp */
133       for (i = 0; i < str_len (oldpos); i++) {
134         if (oldpos[i] == ' ' && spaces[&(oldpos[i])-line] != 0) {
135           debug_print (4, ("f=f: DelSp: spaces[%d] forces space removal\n",
136                            &(oldpos[i])-line));
137           continue;
138         }
139         /* print space at oldpos[i] if it was non-trailing */
140         state_putc (oldpos[i], s);
141       }
142     } else
143       /* for no DelSp, just do whole line as per usual */
144       state_puts (oldpos, s);
145     /* fprintf(stderr,"print_flowed_line: `%s'\n",oldpos); */
146     if (pos < line + len)
147       state_putc (' ', s);
148     state_putc ('\n', s);
149     oldpos = pos;
150   }
151 }
152
153 int rfc3676_handler (BODY * a, STATE * s) {
154   int bytes = a->length;
155   char buf[LONG_STRING];
156   char *curline = str_dup ("");
157   char *t = NULL;
158   unsigned int curline_len = 1, space_len = 1,
159                quotelevel = 0, newql = 0;
160   int buf_off, buf_len;
161   int delsp = 0;
162   int* spaces = NULL;
163
164   /* respect DelSP of RfC3676 only with f=f parts */
165   if ((t = (char*) mutt_get_parameter ("delsp", a->parameter))) {
166     delsp = str_len (t) == 3 && ascii_strncasecmp (t, "yes", 3) == 0;
167     t = NULL;
168   }
169
170   debug_print (2, ("f=f: DelSp: %s\n", delsp ? "yes" : "no"));
171
172   while (bytes > 0 && fgets (buf, sizeof (buf), s->fpin)) {
173     buf_len = str_len (buf);
174     bytes -= buf_len;
175
176     newql = get_quote_level (buf);
177
178     /* a change of quoting level in a paragraph - shouldn't happen, 
179      * but has to be handled - see RFC 3676, sec. 4.5.
180      */
181     if (newql != quotelevel && curline && *curline) {
182       print_flowed_line (curline, s, quotelevel, delsp, spaces, space_len);
183       *curline = '\0';
184       curline_len = 1;
185       space_len = 0;
186     }
187     quotelevel = newql;
188
189     /* XXX - If a line is longer than buf (shouldn't happen), it is split.
190      * This will almost always cause an unintended line break, and 
191      * possibly a change in quoting level. But that's better than not
192      * displaying it at all.
193      */
194     if ((t = strrchr (buf, '\n')) || (t = strrchr (buf, '\r'))) {
195       *t = '\0';
196       buf_len = t - buf;
197     }
198     buf_off = newql;
199     /* respect space-stuffing */
200     if (buf[buf_off] == ' ')
201       buf_off++;
202
203     /* signature separator also flushes the previous paragraph */
204     if (strcmp(buf + buf_off, "-- ") == 0 && curline && *curline) {
205       print_flowed_line (curline, s, quotelevel, delsp, spaces, space_len);
206       *curline = '\0';
207       curline_len = 1;
208       space_len = 0;
209     }
210
211     mem_realloc (&curline, curline_len + buf_len - buf_off);
212     mem_realloc (&spaces, (curline_len + buf_len - buf_off)*sizeof (int));
213     strcpy (curline + curline_len - 1, buf + buf_off);
214     memset (&spaces[space_len], 0, (buf_len - buf_off)*sizeof (int));
215     curline_len += buf_len - buf_off;
216     space_len += buf_len - buf_off;
217
218     /* if this was a fixed line the paragraph is finished */
219     if (buf_len == 0 || buf[buf_len - 1] != ' ' || strcmp(buf + buf_off, "-- ") == 0) {
220       print_flowed_line (curline, s, quotelevel, delsp, spaces, space_len);
221       *curline = '\0';
222       curline_len = 1;
223       space_len = 0;
224     } else {
225       /* if last line we appended had a space and we have DelSp=yes,
226        * get a 1 into spaces array at proper position so that
227        * print_flowed_line() can handle it; don't kill the space
228        * right here 'cause we maybe need soft linebreaks to search for break
229        */
230       if (delsp && curline && *curline && curline_len-2 >= 0 &&
231           curline[curline_len-2] == ' ') {
232         debug_print (4, ("f=f: DelSp: marking spaces[%d] for later removal\n",
233                          curline_len-2));
234         spaces[curline_len-2] = 1;
235       }
236     }
237
238   }
239   mem_free (&spaces);
240   mem_free (&curline);
241   return (0);
242 }
243
244 void rfc3676_space_stuff (HEADER* hdr) {
245 #if DEBUG
246   int lc = 0;
247   size_t len = 0;
248   unsigned char c = '\0';
249 #endif
250   FILE* in = NULL, *out = NULL;
251   char buf[LONG_STRING];
252   char tmpfile[_POSIX_PATH_MAX];
253
254   if (!hdr || !hdr->content || !hdr->content->filename)
255     return;
256
257   debug_print (2, ("f=f: postprocess %s\n", hdr->content->filename));
258   if ((in = safe_fopen (hdr->content->filename, "r")) == NULL)
259     return;
260   mutt_mktemp (tmpfile);
261   if ((out = safe_fopen (tmpfile, "w+")) == NULL) {
262     fclose (in);
263     return;
264   }
265
266   while (fgets (buf, sizeof (buf), in)) {
267     if (ascii_strncmp ("From ", buf, 4) == 0 || buf[0] == ' ') {
268       fputc (' ', out);
269 #if DEBUG
270       lc++;
271       len = str_len (buf);
272       if (len > 0) {
273         c = buf[len-1];
274         buf[len-1] = '\0';
275       }
276       debug_print (4, ("f=f: line %d needs space-stuffing: '%s'\n",
277                        lc, buf));
278       if (len > 0)
279         buf[len-1] = c;
280 #endif
281     }
282     fputs (buf, out);
283   }
284   fclose (in);
285   unlink (hdr->content->filename);
286   fclose (out);
287   str_replace (&hdr->content->filename, tmpfile);
288 }