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