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.
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
40 struct quoteflt
*self
;
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
);
56 _qf_dump_prefix(struct quoteflt
*self
)
60 if (self
->qf_pfix_len
> 0) {
61 rv
= fwrite(self
->qf_pfix
, sizeof *self
->qf_pfix
, self
->qf_pfix_len
,
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
);
81 _qf_add_data(struct quoteflt
*self
, wchar_t wc
)
88 save_l
= save_w
= 0; /* silence cc */
95 if (wc
== L
'\r') /* TODO CR should be stripped in lower level!! */
99 self
->qf_dat
.s
[self
->qf_dat
.l
++] = '?';
101 l
= wctomb(self
->qf_dat
.s
+ self
->qf_dat
.l
, wc
);
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
++] = '\\';
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..) */
148 self
->qf_state
= _QF_PREFIX
;
150 self
->qf_currq
.l
= 0;
157 _qf_state_prefix(struct qf_vc
*vc
)
159 struct quoteflt
*self
= vc
->self
;
161 /*bool_t any = FAL0, lws = FAL0;*/
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];
176 self
->qf_mbps
[1] = self
->qf_mbps
[0];
177 if (i
== (size_t)-2) {
178 /* Redundant shift sequence, out of buffer */
191 if (i
== 1 && ISQUOTE(wc
)) {
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
;
199 self
->qf_currq
.s
[self
->qf_currq
.l
++] = buf
[-1];
203 /* The quote is parsed and compressed; dump it */
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
);
219 _qf_state_data(struct qf_vc
*vc
)
221 struct quoteflt
*self
= vc
->self
;
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];
237 self
->qf_mbps
[1] = self
->qf_mbps
[0];
238 if (i
== (size_t)-2) {
239 /* Redundant shift sequence, out of buffer */
246 { ssize_t j
= _qf_add_data(self
, wc
);
254 if (self
->qf_state
!= _QF_DATA
)
262 #endif /* HAVE_QUOTE_FOLD */
265 quoteflt_dummy(void) /* TODO LEGACY */
267 static struct quoteflt qf_i
;
273 quoteflt_init(struct quoteflt
*self
, char const *prefix
)
275 #ifdef HAVE_QUOTE_FOLD
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);
296 qmin
= (ui_it
)strtol(xcp
+ 1, NULL
, 10);
297 if (qmin
< qmax
>> 1)
299 else if (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
);
313 quoteflt_destroy(struct quoteflt
*self
) /* xxx inline */
319 quoteflt_reset(struct quoteflt
*self
, FILE *f
) /* xxx inline */
322 #ifdef HAVE_QUOTE_FOLD
323 self
->qf_state
= _QF_CLEAN
;
325 self
->qf_currq
.l
= 0;
326 memset(self
->qf_mbps
, 0, sizeof self
->qf_mbps
);
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 ..) */
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 */
345 #ifdef HAVE_QUOTE_FOLD
346 if (self
->qf_qfold_max
== 0)
351 bool_t pxok
= (self
->qf_qfold_min
!= 0);
355 i
= fwrite(self
->qf_pfix
, sizeof *self
->qf_pfix
, self
->qf_pfix_len
,
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
)
372 ll
= (size_t)((char*)vp
- dat
) + 1;
375 i
= fwrite(dat
, sizeof *dat
, ll
, self
->qf_os
);
379 if ((len
-= ll
) == 0)
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
405 switch (self
->qf_state
) {
408 i
= _qf_state_prefix(&vc
);
410 default: /* silence cc (`i' unused) */
412 i
= _qf_state_data(&vc
);
420 #endif /* HAVE_QUOTE_FOLD */
431 quoteflt_flush(struct quoteflt
*self
)
436 #ifdef HAVE_QUOTE_FOLD
437 if (self
->qf_dat
.l
> 0) {
438 rv
= _qf_dump_prefix(self
);
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
;
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
;
453 /* vim:set fenc=utf-8:s-it-mode */