1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2013 - 2014 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 #ifndef HAVE_AMALGAMATION
28 * TODO quotation filter: anticipate in future data: don't break if only WS
29 * TODO or a LF escaping \ follows on the line (simply reuse the latter).
32 #ifdef HAVE_QUOTE_FOLD
42 struct quoteflt
*self
;
47 /* Print out prefix and current quote */
48 static ssize_t
_qf_dump_prefix(struct quoteflt
*self
);
50 /* Add one data character */
51 static ssize_t
_qf_add_data(struct quoteflt
*self
, wchar_t wc
);
53 /* State machine handlers */
54 static ssize_t
_qf_state_prefix(struct qf_vc
*vc
);
55 static ssize_t
_qf_state_data(struct qf_vc
*vc
);
58 _qf_dump_prefix(struct quoteflt
*self
)
64 if ((i
= self
->qf_pfix_len
) > 0 && i
!= fwrite(self
->qf_pfix
, 1, i
,
69 if ((i
= self
->qf_currq
.l
) > 0 && i
!= fwrite(self
->qf_currq
.s
, 1, i
,
82 _qf_add_data(struct quoteflt
*self
, wchar_t wc
)
85 ui32_t save_l
, save_w
;
90 save_l
= save_w
= 0; /* silence cc */
92 /* <newline> ends state */
95 if (wc
== L
'\r') /* TODO CR should be stripped in lower level!! */
98 /* Unroll <tab> to spaces */
100 save_l
= self
->qf_datw
;
101 save_w
= (save_l
+ QUOTE_TAB_SPACES
) & ~(QUOTE_TAB_SPACES
- 1);
103 while (save_w
-- > 0) {
104 ssize_t j
= _qf_add_data(self
, L
' ');
118 self
->qf_dat
.s
[self
->qf_dat
.l
++] = '?';
120 l
= wctomb(self
->qf_dat
.s
+ self
->qf_dat
.l
, wc
);
123 self
->qf_datw
+= (ui32_t
)w
;
124 self
->qf_dat
.l
+= (size_t)l
;
127 /* TODO The last visual may excess (adjusted!) *qfold-max* if it's a wide;
128 * TODO place it on the next line, break before */
129 if (self
->qf_datw
>= self
->qf_qfold_max
) {
130 /* If we have seen a nice breakpoint during traversal, shuffle data
131 * around a bit so as to restore the trailing part after flushing */
132 if (self
->qf_brkl
> 0) {
133 save_w
= self
->qf_datw
- self
->qf_brkw
;
134 save_l
= self
->qf_dat
.l
- self
->qf_brkl
;
135 save_b
= self
->qf_dat
.s
+ self
->qf_brkl
+ 2;
136 memmove(save_b
, save_b
- 2, save_l
);
137 self
->qf_dat
.l
= self
->qf_brkl
;
140 self
->qf_dat
.s
[self
->qf_dat
.l
++] = '\\';
142 self
->qf_dat
.s
[self
->qf_dat
.l
++] = '\n';
143 rv
= quoteflt_flush(self
);
145 /* Restore takeovers, if any */
146 if (save_b
!= NULL
) {
147 self
->qf_brk_isws
= FAL0
;
148 self
->qf_datw
+= save_w
;
149 self
->qf_dat
.l
= save_l
;
150 memmove(self
->qf_dat
.s
, save_b
, save_l
);
152 } else if (self
->qf_datw
>= self
->qf_qfold_min
&& !self
->qf_brk_isws
) {
153 bool_t isws
= iswspace(wc
);
155 if ((isws
&& !self
->qf_brk_isws
) || self
->qf_brkl
== 0) {
156 self
->qf_brkl
= self
->qf_dat
.l
;
157 self
->qf_brkw
= self
->qf_datw
;
158 self
->qf_brk_isws
= isws
;
162 /* If state changed to prefix, perform full reset (note this implies that
163 * quoteflt_flush() performs too much work..) */
165 self
->qf_state
= _QF_PREFIX
;
166 self
->qf_wscnt
= self
->qf_datw
= 0;
167 self
->qf_currq
.l
= 0;
175 _qf_state_prefix(struct qf_vc
*vc
)
177 struct quoteflt
*self
;
187 for (buf
= vc
->buf
, len
= vc
->len
; len
> 0;) {
189 i
= mbrtowc(&wc
, buf
, len
, self
->qf_mbps
);
190 if (i
== (size_t)-1) {
191 /* On hard error, don't modify mbstate_t and step one byte */
192 self
->qf_mbps
[0] = self
->qf_mbps
[1];
198 self
->qf_mbps
[1] = self
->qf_mbps
[0];
199 if (i
== (size_t)-2) {
200 /* Redundant shift sequence, out of buffer */
213 if (i
== 1 && ISQUOTE(wc
)) {
215 if (self
->qf_currq
.l
>= QUOTE_MAX
- 3) {
216 self
->qf_currq
.s
[QUOTE_MAX
- 3] = '.';
217 self
->qf_currq
.s
[QUOTE_MAX
- 2] = '.';
218 self
->qf_currq
.s
[QUOTE_MAX
- 1] = '.';
219 self
->qf_currq
.l
= QUOTE_MAX
;
221 self
->qf_currq
.s
[self
->qf_currq
.l
++] = buf
[-1];
225 /* The quote is parsed and compressed; dump it */
227 self
->qf_datw
= self
->qf_pfix_len
+ self
->qf_currq
.l
;
228 self
->qf_state
= _QF_DATA
;
229 /* Overtake WS (xxx but we de-facto "normalize" to ASCII SP here) */
230 while (self
->qf_wscnt
-- > 0 && self
->qf_currq
.l
< QUOTE_MAX
)
231 self
->qf_currq
.s
[self
->qf_currq
.l
++] = ' ';
233 rv
= _qf_add_data(self
, wc
);
244 _qf_state_data(struct qf_vc
*vc
)
246 struct quoteflt
*self
;
256 for (buf
= vc
->buf
, len
= vc
->len
; len
> 0;) {
258 i
= mbrtowc(&wc
, buf
, len
, self
->qf_mbps
);
259 if (i
== (size_t)-1) {
260 /* On hard error, don't modify mbstate_t and step one byte */
261 self
->qf_mbps
[0] = self
->qf_mbps
[1];
266 self
->qf_mbps
[1] = self
->qf_mbps
[0];
267 if (i
== (size_t)-2) {
268 /* Redundant shift sequence, out of buffer */
275 { ssize_t j
= _qf_add_data(self
, wc
);
283 if (self
->qf_state
!= _QF_DATA
)
292 #endif /* HAVE_QUOTE_FOLD */
295 quoteflt_dummy(void) /* TODO LEGACY (until filters are plugged when needed) */
297 static struct quoteflt qf_i
;
303 quoteflt_init(struct quoteflt
*self
, char const *prefix
)
305 #ifdef HAVE_QUOTE_FOLD
310 memset(self
, 0, sizeof *self
);
312 if ((self
->qf_pfix
= prefix
) != NULL
)
313 self
->qf_pfix_len
= (ui32_t
)strlen(prefix
);
315 /* Check wether the user wants the more fancy quoting algorithm */
316 /* TODO *quote-fold*: QUOTE_MAX may excess it! */
317 #ifdef HAVE_QUOTE_FOLD
318 if (self
->qf_pfix_len
> 0 && (cp
= ok_vlook(quote_fold
)) != NULL
) {
319 ui32_t qmin
, qmax
= (ui32_t
)strtol(cp
, &xcp
, 10);
320 /* These magic values ensure we don't bail :) */
321 if (qmax
< self
->qf_pfix_len
+ 6)
322 qmax
= self
->qf_pfix_len
+ 6;
323 --qmax
; /* The newline escape */
324 if (cp
== xcp
|| *xcp
== '\0')
325 qmin
= (qmax
>> 1) + (qmax
>> 2) + (qmax
>> 5);
327 qmin
= (ui32_t
)strtol(xcp
+ 1, NULL
, 10);
328 if (qmin
< qmax
>> 1)
330 else if (qmin
> qmax
- 2)
333 self
->qf_qfold_min
= qmin
;
334 self
->qf_qfold_max
= qmax
;
336 /* Add pad for takeover copies, backslash and newline */
337 self
->qf_dat
.s
= salloc((qmax
+ 3) * mb_cur_max
);
338 self
->qf_currq
.s
= salloc((QUOTE_MAX
+ 1) * mb_cur_max
);
345 quoteflt_destroy(struct quoteflt
*self
) /* xxx inline */
353 quoteflt_reset(struct quoteflt
*self
, FILE *f
) /* xxx inline */
357 #ifdef HAVE_QUOTE_FOLD
358 self
->qf_state
= _QF_CLEAN
;
360 self
->qf_currq
.l
= 0;
361 memset(self
->qf_mbps
, 0, sizeof self
->qf_mbps
);
367 quoteflt_push(struct quoteflt
*self
, char const *dat
, size_t len
)
369 /* (xxx Ideally the actual push() [and flush()] would be functions on their
370 * xxx own, via indirect vtbl call ..) */
377 /* Bypass? XXX Finally, this filter simply should not be used, then */
378 if (self
->qf_pfix_len
== 0) {
379 if (len
!= fwrite(dat
, 1, len
, self
->qf_os
))
383 /* Normal: place *indentprefix* at every BOL */
385 #ifdef HAVE_QUOTE_FOLD
386 if (self
->qf_qfold_max
== 0)
391 bool_t pxok
= (self
->qf_qfold_min
!= 0);
395 ll
= self
->qf_pfix_len
;
396 if (ll
!= fwrite(self
->qf_pfix
, 1, ll
, self
->qf_os
))
402 /* xxx Strictly speaking this is invalid, because only `/' and `.' are
403 * xxx mandated by POSIX.1-2008 as "invariant across all locales
404 * xxx supported"; though there is no charset known which uses this
405 * xxx control char as part of a multibyte character; note that S-nail
406 * XXX (and the Mail codebase as such) do not support EBCDIC */
407 if ((vp
= memchr(dat
, '\n', len
)) == NULL
)
411 ll
= PTR2SIZE((char*)vp
- dat
) + 1;
414 if (ll
!= fwrite(dat
, sizeof *dat
, ll
, self
->qf_os
))
417 if ((len
-= ll
) == 0)
422 self
->qf_qfold_min
= pxok
;
424 /* Overly complicated, though still only line-per-line: *quote-fold*.
425 * - If .qf_currq.l is 0, then we are in a clean state. Reset .qf_mbps;
426 * TODO note this means we assume that lines start with reset escape seq,
427 * TODO but i don't think this is any worse than what we currently do;
428 * TODO in 15.0, with the value carrier, we should carry conversion states
429 * TODO all along, only resetting on error (or at words for header =???=);
430 * TODO this still is weird for error handling, but we need to act more
431 * TODO stream-alike (though in practice i don't think cross-line states
432 * TODO can be found, because of compatibility reasons; however, being
433 * TODO a problem rather than a solution is not a good thing (tm))
434 * - Lookout for a newline */
435 #ifdef HAVE_QUOTE_FOLD
444 switch (self
->qf_state
) {
447 i
= _qf_state_prefix(&vc
);
449 default: /* silence cc (`i' unused) */
451 i
= _qf_state_data(&vc
);
459 #endif /* HAVE_QUOTE_FOLD */
470 quoteflt_flush(struct quoteflt
*self
)
476 #ifdef HAVE_QUOTE_FOLD
477 if (self
->qf_dat
.l
> 0) {
478 rv
= _qf_dump_prefix(self
);
480 size_t i
= self
->qf_dat
.l
;
481 if (i
== fwrite(self
->qf_dat
.s
, 1, i
, self
->qf_os
))
486 self
->qf_brk_isws
= FAL0
;
487 self
->qf_wscnt
= self
->qf_brkl
= self
->qf_brkw
= 0;
488 self
->qf_datw
= self
->qf_pfix_len
+ self
->qf_currq
.l
;