Add *message-id-disable*
[s-mailx.git] / filter.c
blob852897f35bd1923b411ed8f99bc712fd972594d5
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Filter objects.
4 * Copyright (c) 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #include "rcv.h"
21 #ifdef HAVE_QUOTE_FOLD
22 # include <wctype.h>
23 #endif
25 #include "extern.h"
28 * Quotation filter
32 * TODO quotation filter: anticipate in future data: don't break if only WS
33 * TODO or a LF escaping \ follows on the line (simply reuse the latter).
36 #ifdef HAVE_QUOTE_FOLD
37 CTA(QUOTE_MAX > 3);
39 enum qf_state {
40 _QF_CLEAN,
41 _QF_PREFIX,
42 _QF_DATA
45 struct qf_vc {
46 struct quoteflt *self;
47 char const *buf;
48 size_t len;
51 /* Print out prefix and current quote */
52 static ssize_t _qf_dump_prefix(struct quoteflt *self);
54 /* Add one data character */
55 static ssize_t _qf_add_data(struct quoteflt *self, wchar_t wc);
57 /* State machine handlers */
58 static ssize_t _qf_state_prefix(struct qf_vc *vc);
59 static ssize_t _qf_state_data(struct qf_vc *vc);
61 static ssize_t
62 _qf_dump_prefix(struct quoteflt *self)
64 ssize_t rv = 0;
66 if (self->qf_pfix_len > 0) {
67 rv = fwrite(self->qf_pfix, sizeof *self->qf_pfix, self->qf_pfix_len,
68 self->qf_os);
69 if (rv < 0)
70 goto jleave;
73 if (self->qf_currq.l > 0) {
74 ssize_t i = fwrite(self->qf_currq.s, sizeof *self->qf_currq.s,
75 self->qf_currq.l, self->qf_os);
76 if (i < 0) {
77 rv = i;
78 goto jleave;
80 rv += i;
82 jleave:
83 return rv;
86 static ssize_t
87 _qf_add_data(struct quoteflt *self, wchar_t wc)
89 char *save_b;
90 ui_it save_l, save_w;
91 ssize_t rv = 0;
92 int w, l;
94 save_l = save_w = 0; /* silence cc */
95 save_b = NULL;
96 if (wc == L'\n')
97 goto jflush;
99 w = wcwidth(wc);
100 if (w == -1) {
101 if (wc == L'\r') /* TODO CR should be stripped in lower level!! */
102 goto jleave;
103 jbad:
104 ++self->qf_datw;
105 self->qf_dat.s[self->qf_dat.l++] = '?';
106 } else {
107 l = wctomb(self->qf_dat.s + self->qf_dat.l, wc);
108 if (l < 0)
109 goto jbad;
110 self->qf_datw += (ui_it)w;
111 self->qf_dat.l += (size_t)l;
114 /* TODO The last visual may excess *qfold-max* if it's a wide one;
115 * TODO place it on the next line, break before */
116 if (self->qf_datw >= self->qf_qfold_max) {
117 /* If we have seen a nice breakpoint during traversal, shuffle data
118 * around a bit so as to restore the trailing part after flushing */
119 if (self->qf_brkl > 0) {
120 save_w = self->qf_datw - self->qf_brkw;
121 save_l = self->qf_dat.l - self->qf_brkl;
122 save_b = self->qf_dat.s + self->qf_brkl + 2;
123 memmove(save_b, save_b - 2, save_l);
124 self->qf_dat.l = self->qf_brkl;
127 self->qf_dat.s[self->qf_dat.l++] = '\\';
128 jflush:
129 self->qf_dat.s[self->qf_dat.l++] = '\n';
130 rv = quoteflt_flush(self);
132 /* Restore takeovers, if any */
133 if (save_b != NULL) {
134 self->qf_brk_isws = FAL0;
135 self->qf_datw += save_w;
136 self->qf_dat.l = save_l;
137 memmove(self->qf_dat.s, save_b, save_l);
140 } else if (self->qf_datw >= self->qf_qfold_min && ! self->qf_brk_isws) {
141 bool_t isws = iswspace(wc);
143 if ((isws && ! self->qf_brk_isws) || self->qf_brkl == 0) {
144 self->qf_brkl = self->qf_dat.l;
145 self->qf_brkw = self->qf_datw;
146 self->qf_brk_isws = isws;
151 /* If state changed to prefix, perform full reset (note this implies that
152 * quoteflt_flush() performs too much work..) */
153 if (wc == '\n') {
154 self->qf_state = _QF_PREFIX;
155 self->qf_datw = 0;
156 self->qf_currq.l = 0;
158 jleave:
159 return rv;
162 static ssize_t
163 _qf_state_prefix(struct qf_vc *vc)
165 struct quoteflt *self = vc->self;
166 ssize_t rv = 0;
167 /*bool_t any = FAL0, lws = FAL0;*/
168 char const *buf;
169 size_t len, i;
170 wchar_t wc;
172 for (buf = vc->buf, len = vc->len; len > 0; /*any = TRU1*/) {
173 /* TODO NULL BYTE! */
174 i = mbrtowc(&wc, buf, len, self->qf_mbps);
175 if (i == (size_t)-1) {
176 /* On hard error, don't modify mbstate_t and step one byte */
177 self->qf_mbps[0] = self->qf_mbps[1];
178 ++buf;
179 --len;
180 continue;
182 self->qf_mbps[1] = self->qf_mbps[0];
183 if (i == (size_t)-2) {
184 /* Redundant shift sequence, out of buffer */
185 len = 0;
186 break;
188 buf += i;
189 len -= i;
191 if (wc == L'\n')
192 goto jfin;
193 if (iswspace(wc)) {
194 /*lws = TRU1;*/
195 continue;
197 if (i == 1 && ISQUOTE(wc)) {
198 /*lws = FAL0;*/
199 if (self->qf_currq.l >= QUOTE_MAX - 3) {
200 self->qf_currq.s[QUOTE_MAX - 3] = '.';
201 self->qf_currq.s[QUOTE_MAX - 2] = '.';
202 self->qf_currq.s[QUOTE_MAX - 1] = '.';
203 self->qf_currq.l = QUOTE_MAX;
204 } else
205 self->qf_currq.s[self->qf_currq.l++] = buf[-1];
206 continue;
209 /* The quote is parsed and compressed; dump it */
210 jfin:
211 /*if (lws && any && self->qf_currq.l > 0)
212 self->qf_currq.s[self->qf_currq.l++] = ' ';*/
213 self->qf_datw = self->qf_pfix_len + self->qf_currq.l;
214 self->qf_state = _QF_DATA;
215 rv =_qf_add_data(self, wc);
216 break;
219 vc->buf = buf;
220 vc->len = len;
221 return rv;
224 static ssize_t
225 _qf_state_data(struct qf_vc *vc)
227 struct quoteflt *self = vc->self;
228 ssize_t rv = 0;
229 char const *buf;
230 size_t len, i;
231 wchar_t wc;
233 for (buf = vc->buf, len = vc->len; len > 0;) {
234 /* TODO NULL BYTE! */
235 i = mbrtowc(&wc, buf, len, self->qf_mbps);
236 if (i == (size_t)-1) {
237 /* On hard error, don't modify mbstate_t and step one byte */
238 self->qf_mbps[0] = self->qf_mbps[1];
239 ++buf;
240 --len;
241 continue;
243 self->qf_mbps[1] = self->qf_mbps[0];
244 if (i == (size_t)-2) {
245 /* Redundant shift sequence, out of buffer */
246 len = 0;
247 break;
249 buf += i;
250 len -= i;
252 { ssize_t j = _qf_add_data(self, wc);
253 if (j < 0) {
254 rv = j;
255 break;
257 rv += j;
260 if (self->qf_state != _QF_DATA)
261 break;
264 vc->buf = buf;
265 vc->len = len;
266 return rv;
268 #endif /* HAVE_QUOTE_FOLD */
270 struct quoteflt *
271 quoteflt_dummy(void) /* TODO LEGACY */
273 static struct quoteflt qf_i;
275 return &qf_i;
278 void
279 quoteflt_init(struct quoteflt *self, char const *prefix)
281 #ifdef HAVE_QUOTE_FOLD
282 char *xcp, *cp;
283 #endif
285 memset(self, 0, sizeof *self);
287 if ((self->qf_pfix = prefix) != NULL)
288 self->qf_pfix_len = (ui_it)strlen(prefix);
290 /* Check wether the user wants the more fancy quoting algorithm */
291 /* TODO *quote-fold*: QUOTE_MAX may excess it! */
292 #ifdef HAVE_QUOTE_FOLD
293 if ((cp = voption("quote-fold")) != NULL) {
294 ui_it qmin, qmax = (ui_it)strtol(cp, (char**)&xcp, 10);
295 /* These magic values ensure we don't bail :) */
296 if (qmax < self->qf_pfix_len + 6)
297 qmax = self->qf_pfix_len + 6;
298 --qmax; /* The newline escape */
299 if (cp == xcp || *xcp == '\0')
300 qmin = (qmax >> 1) + (qmax >> 2) + (qmax >> 5);
301 else {
302 qmin = (ui_it)strtol(xcp + 1, NULL, 10);
303 if (qmin < qmax >> 1)
304 qmin = qmax >> 1;
305 else if (qmin > qmax - 2)
306 qmin = qmax - 2;
308 self->qf_qfold_min = qmin;
309 self->qf_qfold_max = qmax;
311 /* Add pad for takeover copies, backslash and newline */
312 self->qf_dat.s = salloc((qmax + 3) * mb_cur_max);
313 self->qf_currq.s = salloc((QUOTE_MAX + 1) * mb_cur_max);
315 #endif
318 void
319 quoteflt_destroy(struct quoteflt *self) /* xxx inline */
321 (void)self;
324 void
325 quoteflt_reset(struct quoteflt *self, FILE *f) /* xxx inline */
327 self->qf_os = f;
328 #ifdef HAVE_QUOTE_FOLD
329 self->qf_state = _QF_CLEAN;
330 self->qf_dat.l =
331 self->qf_currq.l = 0;
332 memset(self->qf_mbps, 0, sizeof self->qf_mbps);
333 #endif
336 ssize_t
337 quoteflt_push(struct quoteflt *self, char const *dat, size_t len)
339 /* (xxx Ideally the actual push() [and flush()] would be functions on their
340 * xxx own, via indirect vtbl call ..) */
341 ssize_t i, rv = 0;
343 if (len == 0)
344 goto jleave;
346 /* Bypass? XXX Finally, this filter simply should not be used, then */
347 if (self->qf_pfix_len == 0)
348 rv = fwrite(dat, sizeof *dat, len, self->qf_os);
349 /* Normal: place *indentprefix* at every BOL */
350 else
351 #ifdef HAVE_QUOTE_FOLD
352 if (self->qf_qfold_max == 0)
353 #endif
355 void *vp;
356 size_t ll;
357 bool_t pxok = (self->qf_qfold_min != 0);
359 for (;;) {
360 if (! pxok) {
361 i = fwrite(self->qf_pfix, sizeof *self->qf_pfix, self->qf_pfix_len,
362 self->qf_os);
363 if (i < 0)
364 goto jerr;
365 rv += i;
366 pxok = TRU1;
369 /* xxx Strictly speaking this is invalid, because only `/' and `.' are
370 * xxx mandated by POSIX.1-2008 as "invariant across all locales
371 * xxx supported"; though there is no charset known which uses this
372 * xxx control char as part of a multibyte character; note that S-nail
373 * XXX (and the Mail codebase as such) do not support EBCDIC */
374 if ((vp = memchr(dat, '\n', len)) == NULL)
375 ll = len;
376 else {
377 pxok = FAL0;
378 ll = (size_t)((char*)vp - dat) + 1;
381 i = fwrite(dat, sizeof *dat, ll, self->qf_os);
382 if (i < 0)
383 goto jerr;
384 rv += i;
385 if ((len -= ll) == 0)
386 break;
387 dat += ll;
390 self->qf_qfold_min = pxok;
392 /* Overly complicated, though still only line-per-line: *quote-fold*.
393 * - If .qf_currq.l is 0, then we are in a clean state. Reset .qf_mbps;
394 * TODO note this means we assume that lines start with reset escape seq,
395 * TODO but i don't think this is any worse than what we currently do;
396 * TODO in 15.0, with the value carrier, we should carry conversion states
397 * TODO all along, only resetting on error (or at words for header =???=);
398 * TODO this still is weird for error handling, but we need to act more
399 * TODO stream-alike (though in practice i don't think cross-line states
400 * TODO can be found, because of compatibility reasons; however, being
401 * TODO a problem rather than a solution is not a good thing (tm))
402 * - Lookout for a newline */
403 #ifdef HAVE_QUOTE_FOLD
404 else {
405 struct qf_vc vc;
407 vc.self = self;
408 vc.buf = dat;
409 vc.len = len;
410 while (vc.len > 0) {
411 switch (self->qf_state) {
412 case _QF_CLEAN:
413 case _QF_PREFIX:
414 i = _qf_state_prefix(&vc);
415 break;
416 default: /* silence cc (`i' unused) */
417 case _QF_DATA:
418 i = _qf_state_data(&vc);
419 break;
421 if (i < 0)
422 goto jerr;
423 rv += i;
426 #endif /* HAVE_QUOTE_FOLD */
428 jleave:
429 return rv;
431 jerr:
432 rv = -1;
433 goto jleave;
436 ssize_t
437 quoteflt_flush(struct quoteflt *self)
439 ssize_t rv = 0;
440 (void)self;
442 #ifdef HAVE_QUOTE_FOLD
443 if (self->qf_dat.l > 0) {
444 rv = _qf_dump_prefix(self);
445 if (rv >= 0) {
446 ssize_t i = fwrite(self->qf_dat.s, sizeof *self->qf_dat.s,
447 self->qf_dat.l, self->qf_os);
448 rv = (i < 0) ? i : rv + i;
449 self->qf_dat.l = 0;
450 self->qf_datw = self->qf_pfix_len + self->qf_currq.l;
451 self->qf_brkl = self->qf_brkw = 0;
452 self->qf_brk_isws = FAL0;
455 #endif
456 return rv;
459 /* vim:set fenc=utf-8:s-it-mode */