Localize cmdtab[] in lex.c..
[s-mailx.git] / filter.c
blob689ef62f4a1cbd28bc54987dd6b79e20d8dc8129
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 "nail.h"
22 * Quotation filter
26 * TODO quotation filter: anticipate in future data: don't break if only WS
27 * TODO or a LF escaping \ follows on the line (simply reuse the latter).
30 #ifdef HAVE_QUOTE_FOLD
31 CTA(QUOTE_MAX > 3);
33 enum qf_state {
34 _QF_CLEAN,
35 _QF_PREFIX,
36 _QF_DATA
39 struct qf_vc {
40 struct quoteflt *self;
41 char const *buf;
42 size_t len;
45 /* Print out prefix and current quote */
46 static ssize_t _qf_dump_prefix(struct quoteflt *self);
48 /* Add one data character */
49 static ssize_t _qf_add_data(struct quoteflt *self, wchar_t wc);
51 /* State machine handlers */
52 static ssize_t _qf_state_prefix(struct qf_vc *vc);
53 static ssize_t _qf_state_data(struct qf_vc *vc);
55 static ssize_t
56 _qf_dump_prefix(struct quoteflt *self)
58 ssize_t rv = 0;
60 if (self->qf_pfix_len > 0) {
61 rv = fwrite(self->qf_pfix, sizeof *self->qf_pfix, self->qf_pfix_len,
62 self->qf_os);
63 if (rv < 0)
64 goto jleave;
67 if (self->qf_currq.l > 0) {
68 ssize_t i = fwrite(self->qf_currq.s, sizeof *self->qf_currq.s,
69 self->qf_currq.l, self->qf_os);
70 if (i < 0) {
71 rv = i;
72 goto jleave;
74 rv += i;
76 jleave:
77 return rv;
80 static ssize_t
81 _qf_add_data(struct quoteflt *self, wchar_t wc)
83 char *save_b;
84 ui_it save_l, save_w;
85 ssize_t rv = 0;
86 int w, l;
88 save_l = save_w = 0; /* silence cc */
89 save_b = NULL;
90 if (wc == L'\n')
91 goto jflush;
93 w = wcwidth(wc);
94 if (w == -1) {
95 if (wc == L'\r') /* TODO CR should be stripped in lower level!! */
96 goto jleave;
97 jbad:
98 ++self->qf_datw;
99 self->qf_dat.s[self->qf_dat.l++] = '?';
100 } else {
101 l = wctomb(self->qf_dat.s + self->qf_dat.l, wc);
102 if (l < 0)
103 goto jbad;
104 self->qf_datw += (ui_it)w;
105 self->qf_dat.l += (size_t)l;
108 /* TODO The last visual may excess *qfold-max* if it's a wide one;
109 * TODO place it on the next line, break before */
110 if (self->qf_datw >= self->qf_qfold_max) {
111 /* If we have seen a nice breakpoint during traversal, shuffle data
112 * around a bit so as to restore the trailing part after flushing */
113 if (self->qf_brkl > 0) {
114 save_w = self->qf_datw - self->qf_brkw;
115 save_l = self->qf_dat.l - self->qf_brkl;
116 save_b = self->qf_dat.s + self->qf_brkl + 2;
117 memmove(save_b, save_b - 2, save_l);
118 self->qf_dat.l = self->qf_brkl;
121 self->qf_dat.s[self->qf_dat.l++] = '\\';
122 jflush:
123 self->qf_dat.s[self->qf_dat.l++] = '\n';
124 rv = quoteflt_flush(self);
126 /* Restore takeovers, if any */
127 if (save_b != NULL) {
128 self->qf_brk_isws = FAL0;
129 self->qf_datw += save_w;
130 self->qf_dat.l = save_l;
131 memmove(self->qf_dat.s, save_b, save_l);
134 } else if (self->qf_datw >= self->qf_qfold_min && ! self->qf_brk_isws) {
135 bool_t isws = iswspace(wc);
137 if ((isws && ! self->qf_brk_isws) || self->qf_brkl == 0) {
138 self->qf_brkl = self->qf_dat.l;
139 self->qf_brkw = self->qf_datw;
140 self->qf_brk_isws = isws;
145 /* If state changed to prefix, perform full reset (note this implies that
146 * quoteflt_flush() performs too much work..) */
147 if (wc == '\n') {
148 self->qf_state = _QF_PREFIX;
149 self->qf_datw = 0;
150 self->qf_currq.l = 0;
152 jleave:
153 return rv;
156 static ssize_t
157 _qf_state_prefix(struct qf_vc *vc)
159 struct quoteflt *self = vc->self;
160 ssize_t rv = 0;
161 /*bool_t any = FAL0, lws = FAL0;*/
162 char const *buf;
163 size_t len, i;
164 wchar_t wc;
166 for (buf = vc->buf, len = vc->len; len > 0; /*any = TRU1*/) {
167 /* TODO NULL BYTE! */
168 i = mbrtowc(&wc, buf, len, self->qf_mbps);
169 if (i == (size_t)-1) {
170 /* On hard error, don't modify mbstate_t and step one byte */
171 self->qf_mbps[0] = self->qf_mbps[1];
172 ++buf;
173 --len;
174 continue;
176 self->qf_mbps[1] = self->qf_mbps[0];
177 if (i == (size_t)-2) {
178 /* Redundant shift sequence, out of buffer */
179 len = 0;
180 break;
182 buf += i;
183 len -= i;
185 if (wc == L'\n')
186 goto jfin;
187 if (iswspace(wc)) {
188 /*lws = TRU1;*/
189 continue;
191 if (i == 1 && ISQUOTE(wc)) {
192 /*lws = FAL0;*/
193 if (self->qf_currq.l >= QUOTE_MAX - 3) {
194 self->qf_currq.s[QUOTE_MAX - 3] = '.';
195 self->qf_currq.s[QUOTE_MAX - 2] = '.';
196 self->qf_currq.s[QUOTE_MAX - 1] = '.';
197 self->qf_currq.l = QUOTE_MAX;
198 } else
199 self->qf_currq.s[self->qf_currq.l++] = buf[-1];
200 continue;
203 /* The quote is parsed and compressed; dump it */
204 jfin:
205 /*if (lws && any && self->qf_currq.l > 0)
206 self->qf_currq.s[self->qf_currq.l++] = ' ';*/
207 self->qf_datw = self->qf_pfix_len + self->qf_currq.l;
208 self->qf_state = _QF_DATA;
209 rv =_qf_add_data(self, wc);
210 break;
213 vc->buf = buf;
214 vc->len = len;
215 return rv;
218 static ssize_t
219 _qf_state_data(struct qf_vc *vc)
221 struct quoteflt *self = vc->self;
222 ssize_t rv = 0;
223 char const *buf;
224 size_t len, i;
225 wchar_t wc;
227 for (buf = vc->buf, len = vc->len; len > 0;) {
228 /* TODO NULL BYTE! */
229 i = mbrtowc(&wc, buf, len, self->qf_mbps);
230 if (i == (size_t)-1) {
231 /* On hard error, don't modify mbstate_t and step one byte */
232 self->qf_mbps[0] = self->qf_mbps[1];
233 ++buf;
234 --len;
235 continue;
237 self->qf_mbps[1] = self->qf_mbps[0];
238 if (i == (size_t)-2) {
239 /* Redundant shift sequence, out of buffer */
240 len = 0;
241 break;
243 buf += i;
244 len -= i;
246 { ssize_t j = _qf_add_data(self, wc);
247 if (j < 0) {
248 rv = j;
249 break;
251 rv += j;
254 if (self->qf_state != _QF_DATA)
255 break;
258 vc->buf = buf;
259 vc->len = len;
260 return rv;
262 #endif /* HAVE_QUOTE_FOLD */
264 struct quoteflt *
265 quoteflt_dummy(void) /* TODO LEGACY */
267 static struct quoteflt qf_i;
269 return &qf_i;
272 void
273 quoteflt_init(struct quoteflt *self, char const *prefix)
275 #ifdef HAVE_QUOTE_FOLD
276 char *xcp, *cp;
277 #endif
279 memset(self, 0, sizeof *self);
281 if ((self->qf_pfix = prefix) != NULL)
282 self->qf_pfix_len = (ui_it)strlen(prefix);
284 /* Check wether the user wants the more fancy quoting algorithm */
285 /* TODO *quote-fold*: QUOTE_MAX may excess it! */
286 #ifdef HAVE_QUOTE_FOLD
287 if ((cp = voption("quote-fold")) != NULL) {
288 ui_it qmin, qmax = (ui_it)strtol(cp, (char**)&xcp, 10);
289 /* These magic values ensure we don't bail :) */
290 if (qmax < self->qf_pfix_len + 6)
291 qmax = self->qf_pfix_len + 6;
292 --qmax; /* The newline escape */
293 if (cp == xcp || *xcp == '\0')
294 qmin = (qmax >> 1) + (qmax >> 2) + (qmax >> 5);
295 else {
296 qmin = (ui_it)strtol(xcp + 1, NULL, 10);
297 if (qmin < qmax >> 1)
298 qmin = qmax >> 1;
299 else if (qmin > qmax - 2)
300 qmin = qmax - 2;
302 self->qf_qfold_min = qmin;
303 self->qf_qfold_max = qmax;
305 /* Add pad for takeover copies, backslash and newline */
306 self->qf_dat.s = salloc((qmax + 3) * mb_cur_max);
307 self->qf_currq.s = salloc((QUOTE_MAX + 1) * mb_cur_max);
309 #endif
312 void
313 quoteflt_destroy(struct quoteflt *self) /* xxx inline */
315 (void)self;
318 void
319 quoteflt_reset(struct quoteflt *self, FILE *f) /* xxx inline */
321 self->qf_os = f;
322 #ifdef HAVE_QUOTE_FOLD
323 self->qf_state = _QF_CLEAN;
324 self->qf_dat.l =
325 self->qf_currq.l = 0;
326 memset(self->qf_mbps, 0, sizeof self->qf_mbps);
327 #endif
330 ssize_t
331 quoteflt_push(struct quoteflt *self, char const *dat, size_t len)
333 /* (xxx Ideally the actual push() [and flush()] would be functions on their
334 * xxx own, via indirect vtbl call ..) */
335 ssize_t i, rv = 0;
337 if (len == 0)
338 goto jleave;
340 /* Bypass? XXX Finally, this filter simply should not be used, then */
341 if (self->qf_pfix_len == 0)
342 rv = fwrite(dat, sizeof *dat, len, self->qf_os);
343 /* Normal: place *indentprefix* at every BOL */
344 else
345 #ifdef HAVE_QUOTE_FOLD
346 if (self->qf_qfold_max == 0)
347 #endif
349 void *vp;
350 size_t ll;
351 bool_t pxok = (self->qf_qfold_min != 0);
353 for (;;) {
354 if (! pxok) {
355 i = fwrite(self->qf_pfix, sizeof *self->qf_pfix, self->qf_pfix_len,
356 self->qf_os);
357 if (i < 0)
358 goto jerr;
359 rv += i;
360 pxok = TRU1;
363 /* xxx Strictly speaking this is invalid, because only `/' and `.' are
364 * xxx mandated by POSIX.1-2008 as "invariant across all locales
365 * xxx supported"; though there is no charset known which uses this
366 * xxx control char as part of a multibyte character; note that S-nail
367 * XXX (and the Mail codebase as such) do not support EBCDIC */
368 if ((vp = memchr(dat, '\n', len)) == NULL)
369 ll = len;
370 else {
371 pxok = FAL0;
372 ll = (size_t)((char*)vp - dat) + 1;
375 i = fwrite(dat, sizeof *dat, ll, self->qf_os);
376 if (i < 0)
377 goto jerr;
378 rv += i;
379 if ((len -= ll) == 0)
380 break;
381 dat += ll;
384 self->qf_qfold_min = pxok;
386 /* Overly complicated, though still only line-per-line: *quote-fold*.
387 * - If .qf_currq.l is 0, then we are in a clean state. Reset .qf_mbps;
388 * TODO note this means we assume that lines start with reset escape seq,
389 * TODO but i don't think this is any worse than what we currently do;
390 * TODO in 15.0, with the value carrier, we should carry conversion states
391 * TODO all along, only resetting on error (or at words for header =???=);
392 * TODO this still is weird for error handling, but we need to act more
393 * TODO stream-alike (though in practice i don't think cross-line states
394 * TODO can be found, because of compatibility reasons; however, being
395 * TODO a problem rather than a solution is not a good thing (tm))
396 * - Lookout for a newline */
397 #ifdef HAVE_QUOTE_FOLD
398 else {
399 struct qf_vc vc;
401 vc.self = self;
402 vc.buf = dat;
403 vc.len = len;
404 while (vc.len > 0) {
405 switch (self->qf_state) {
406 case _QF_CLEAN:
407 case _QF_PREFIX:
408 i = _qf_state_prefix(&vc);
409 break;
410 default: /* silence cc (`i' unused) */
411 case _QF_DATA:
412 i = _qf_state_data(&vc);
413 break;
415 if (i < 0)
416 goto jerr;
417 rv += i;
420 #endif /* HAVE_QUOTE_FOLD */
422 jleave:
423 return rv;
425 jerr:
426 rv = -1;
427 goto jleave;
430 ssize_t
431 quoteflt_flush(struct quoteflt *self)
433 ssize_t rv = 0;
434 (void)self;
436 #ifdef HAVE_QUOTE_FOLD
437 if (self->qf_dat.l > 0) {
438 rv = _qf_dump_prefix(self);
439 if (rv >= 0) {
440 ssize_t i = fwrite(self->qf_dat.s, sizeof *self->qf_dat.s,
441 self->qf_dat.l, self->qf_os);
442 rv = (i < 0) ? i : rv + i;
443 self->qf_dat.l = 0;
444 self->qf_datw = self->qf_pfix_len + self->qf_currq.l;
445 self->qf_brkl = self->qf_brkw = 0;
446 self->qf_brk_isws = FAL0;
449 #endif
450 return rv;
453 /* vim:set fenc=utf-8:s-it-mode */