1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
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 #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
)
63 if ((i
= self
->qf_pfix_len
) > 0 && i
!= fwrite(self
->qf_pfix
, 1, i
,
68 if ((i
= self
->qf_currq
.l
) > 0 && i
!= fwrite(self
->qf_currq
.s
, 1, i
,
80 _qf_add_data(struct quoteflt
*self
, wchar_t wc
)
87 save_l
= save_w
= 0; /* silence cc */
89 /* <newline> ends state */
92 if (wc
== L
'\r') /* TODO CR should be stripped in lower level!! */
95 /* Unroll <tab> to spaces */
97 save_l
= self
->qf_datw
;
98 save_w
= (save_l
+ QUOTE_TAB_SPACES
) & ~(QUOTE_TAB_SPACES
- 1);
100 while (save_w
-- > 0) {
101 ssize_t j
= _qf_add_data(self
, L
' ');
115 self
->qf_dat
.s
[self
->qf_dat
.l
++] = '?';
117 l
= wctomb(self
->qf_dat
.s
+ self
->qf_dat
.l
, wc
);
120 self
->qf_datw
+= (ui_it
)w
;
121 self
->qf_dat
.l
+= (size_t)l
;
124 /* TODO The last visual may excess *qfold-max* if it's a wide one;
125 * TODO place it on the next line, break before */
126 if (self
->qf_datw
>= self
->qf_qfold_max
) {
127 /* If we have seen a nice breakpoint during traversal, shuffle data
128 * around a bit so as to restore the trailing part after flushing */
129 if (self
->qf_brkl
> 0) {
130 save_w
= self
->qf_datw
- self
->qf_brkw
;
131 save_l
= self
->qf_dat
.l
- self
->qf_brkl
;
132 save_b
= self
->qf_dat
.s
+ self
->qf_brkl
+ 2;
133 memmove(save_b
, save_b
- 2, save_l
);
134 self
->qf_dat
.l
= self
->qf_brkl
;
137 self
->qf_dat
.s
[self
->qf_dat
.l
++] = '\\';
139 self
->qf_dat
.s
[self
->qf_dat
.l
++] = '\n';
140 rv
= quoteflt_flush(self
);
142 /* Restore takeovers, if any */
143 if (save_b
!= NULL
) {
144 self
->qf_brk_isws
= FAL0
;
145 self
->qf_datw
+= save_w
;
146 self
->qf_dat
.l
= save_l
;
147 memmove(self
->qf_dat
.s
, save_b
, save_l
);
150 } else if (self
->qf_datw
>= self
->qf_qfold_min
&& ! self
->qf_brk_isws
) {
151 bool_t isws
= iswspace(wc
);
153 if ((isws
&& ! self
->qf_brk_isws
) || self
->qf_brkl
== 0) {
154 self
->qf_brkl
= self
->qf_dat
.l
;
155 self
->qf_brkw
= self
->qf_datw
;
156 self
->qf_brk_isws
= isws
;
160 /* If state changed to prefix, perform full reset (note this implies that
161 * quoteflt_flush() performs too much work..) */
163 self
->qf_state
= _QF_PREFIX
;
164 self
->qf_wscnt
= self
->qf_datw
= 0;
165 self
->qf_currq
.l
= 0;
172 _qf_state_prefix(struct qf_vc
*vc
)
174 struct quoteflt
*self
= vc
->self
;
180 for (buf
= vc
->buf
, len
= vc
->len
; len
> 0;) {
181 /* TODO NULL BYTE! */
182 i
= mbrtowc(&wc
, buf
, len
, self
->qf_mbps
);
183 if (i
== (size_t)-1) {
184 /* On hard error, don't modify mbstate_t and step one byte */
185 self
->qf_mbps
[0] = self
->qf_mbps
[1];
191 self
->qf_mbps
[1] = self
->qf_mbps
[0];
192 if (i
== (size_t)-2) {
193 /* Redundant shift sequence, out of buffer */
206 if (i
== 1 && ISQUOTE(wc
)) {
208 if (self
->qf_currq
.l
>= QUOTE_MAX
- 3) {
209 self
->qf_currq
.s
[QUOTE_MAX
- 3] = '.';
210 self
->qf_currq
.s
[QUOTE_MAX
- 2] = '.';
211 self
->qf_currq
.s
[QUOTE_MAX
- 1] = '.';
212 self
->qf_currq
.l
= QUOTE_MAX
;
214 self
->qf_currq
.s
[self
->qf_currq
.l
++] = buf
[-1];
218 /* The quote is parsed and compressed; dump it */
220 self
->qf_datw
= self
->qf_pfix_len
+ self
->qf_currq
.l
;
221 self
->qf_state
= _QF_DATA
;
222 /* Overtake WS (xxx but we de-facto "normalize" to ASCII SP here) */
223 while (self
->qf_wscnt
-- > 0 && self
->qf_currq
.l
< QUOTE_MAX
)
224 self
->qf_currq
.s
[self
->qf_currq
.l
++] = ' ';
226 rv
= _qf_add_data(self
, wc
);
236 _qf_state_data(struct qf_vc
*vc
)
238 struct quoteflt
*self
= vc
->self
;
244 for (buf
= vc
->buf
, len
= vc
->len
; len
> 0;) {
245 /* TODO NULL BYTE! */
246 i
= mbrtowc(&wc
, buf
, len
, self
->qf_mbps
);
247 if (i
== (size_t)-1) {
248 /* On hard error, don't modify mbstate_t and step one byte */
249 self
->qf_mbps
[0] = self
->qf_mbps
[1];
254 self
->qf_mbps
[1] = self
->qf_mbps
[0];
255 if (i
== (size_t)-2) {
256 /* Redundant shift sequence, out of buffer */
263 { ssize_t j
= _qf_add_data(self
, wc
);
271 if (self
->qf_state
!= _QF_DATA
)
279 #endif /* HAVE_QUOTE_FOLD */
282 quoteflt_dummy(void) /* TODO LEGACY */
284 static struct quoteflt qf_i
;
290 quoteflt_init(struct quoteflt
*self
, char const *prefix
)
292 #ifdef HAVE_QUOTE_FOLD
296 memset(self
, 0, sizeof *self
);
298 if ((self
->qf_pfix
= prefix
) != NULL
)
299 self
->qf_pfix_len
= (ui_it
)strlen(prefix
);
301 /* Check wether the user wants the more fancy quoting algorithm */
302 /* TODO *quote-fold*: QUOTE_MAX may excess it! */
303 #ifdef HAVE_QUOTE_FOLD
304 if (self
->qf_pfix_len
> 0 && (cp
= voption("quote-fold")) != NULL
) {
305 ui_it qmin
, qmax
= (ui_it
)strtol(cp
, (char**)&xcp
, 10);
306 /* These magic values ensure we don't bail :) */
307 if (qmax
< self
->qf_pfix_len
+ 6)
308 qmax
= self
->qf_pfix_len
+ 6;
309 --qmax
; /* The newline escape */
310 if (cp
== xcp
|| *xcp
== '\0')
311 qmin
= (qmax
>> 1) + (qmax
>> 2) + (qmax
>> 5);
313 qmin
= (ui_it
)strtol(xcp
+ 1, NULL
, 10);
314 if (qmin
< qmax
>> 1)
316 else if (qmin
> qmax
- 2)
319 self
->qf_qfold_min
= qmin
;
320 self
->qf_qfold_max
= qmax
;
322 /* Add pad for takeover copies, backslash and newline */
323 self
->qf_dat
.s
= salloc((qmax
+ 3) * mb_cur_max
);
324 self
->qf_currq
.s
= salloc((QUOTE_MAX
+ 1) * mb_cur_max
);
330 quoteflt_destroy(struct quoteflt
*self
) /* xxx inline */
336 quoteflt_reset(struct quoteflt
*self
, FILE *f
) /* xxx inline */
339 #ifdef HAVE_QUOTE_FOLD
340 self
->qf_state
= _QF_CLEAN
;
342 self
->qf_currq
.l
= 0;
343 memset(self
->qf_mbps
, 0, sizeof self
->qf_mbps
);
348 quoteflt_push(struct quoteflt
*self
, char const *dat
, size_t len
)
350 /* (xxx Ideally the actual push() [and flush()] would be functions on their
351 * xxx own, via indirect vtbl call ..) */
357 /* Bypass? XXX Finally, this filter simply should not be used, then */
358 if (self
->qf_pfix_len
== 0) {
359 if (len
!= fwrite(dat
, 1, len
, self
->qf_os
))
363 /* Normal: place *indentprefix* at every BOL */
365 #ifdef HAVE_QUOTE_FOLD
366 if (self
->qf_qfold_max
== 0)
371 bool_t pxok
= (self
->qf_qfold_min
!= 0);
375 ll
= self
->qf_pfix_len
;
376 if (ll
!= fwrite(self
->qf_pfix
, 1, ll
, self
->qf_os
))
382 /* xxx Strictly speaking this is invalid, because only `/' and `.' are
383 * xxx mandated by POSIX.1-2008 as "invariant across all locales
384 * xxx supported"; though there is no charset known which uses this
385 * xxx control char as part of a multibyte character; note that S-nail
386 * XXX (and the Mail codebase as such) do not support EBCDIC */
387 if ((vp
= memchr(dat
, '\n', len
)) == NULL
)
391 ll
= (size_t)((char*)vp
- dat
) + 1;
394 if (ll
!= fwrite(dat
, sizeof *dat
, ll
, self
->qf_os
))
397 if ((len
-= ll
) == 0)
402 self
->qf_qfold_min
= pxok
;
404 /* Overly complicated, though still only line-per-line: *quote-fold*.
405 * - If .qf_currq.l is 0, then we are in a clean state. Reset .qf_mbps;
406 * TODO note this means we assume that lines start with reset escape seq,
407 * TODO but i don't think this is any worse than what we currently do;
408 * TODO in 15.0, with the value carrier, we should carry conversion states
409 * TODO all along, only resetting on error (or at words for header =???=);
410 * TODO this still is weird for error handling, but we need to act more
411 * TODO stream-alike (though in practice i don't think cross-line states
412 * TODO can be found, because of compatibility reasons; however, being
413 * TODO a problem rather than a solution is not a good thing (tm))
414 * - Lookout for a newline */
415 #ifdef HAVE_QUOTE_FOLD
424 switch (self
->qf_state
) {
427 i
= _qf_state_prefix(&vc
);
429 default: /* silence cc (`i' unused) */
431 i
= _qf_state_data(&vc
);
439 #endif /* HAVE_QUOTE_FOLD */
450 quoteflt_flush(struct quoteflt
*self
)
455 #ifdef HAVE_QUOTE_FOLD
456 if (self
->qf_dat
.l
> 0) {
457 rv
= _qf_dump_prefix(self
);
459 size_t i
= self
->qf_dat
.l
;
460 if (i
== fwrite(self
->qf_dat
.s
, 1, i
, self
->qf_os
))
465 self
->qf_brk_isws
= FAL0
;
466 self
->qf_wscnt
= self
->qf_brkl
= self
->qf_brkw
= 0;
467 self
->qf_datw
= self
->qf_pfix_len
+ self
->qf_currq
.l
;
474 /* vim:set fenc=utf-8:s-it-mode */