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.
21 #ifdef HAVE_QUOTE_FOLD
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
46 struct quoteflt
*self
;
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
);
62 _qf_dump_prefix(struct quoteflt
*self
)
66 if (self
->qf_pfix_len
> 0) {
67 rv
= fwrite(self
->qf_pfix
, sizeof *self
->qf_pfix
, self
->qf_pfix_len
,
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
);
87 _qf_add_data(struct quoteflt
*self
, wchar_t wc
)
94 save_l
= save_w
= 0; /* silence cc */
101 if (wc
== L
'\r') /* TODO CR should be stripped in lower level!! */
105 self
->qf_dat
.s
[self
->qf_dat
.l
++] = '?';
107 l
= wctomb(self
->qf_dat
.s
+ self
->qf_dat
.l
, wc
);
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
++] = '\\';
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..) */
154 self
->qf_state
= _QF_PREFIX
;
156 self
->qf_currq
.l
= 0;
163 _qf_state_prefix(struct qf_vc
*vc
)
165 struct quoteflt
*self
= vc
->self
;
167 /*bool_t any = FAL0, lws = FAL0;*/
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];
182 self
->qf_mbps
[1] = self
->qf_mbps
[0];
183 if (i
== (size_t)-2) {
184 /* Redundant shift sequence, out of buffer */
197 if (i
== 1 && ISQUOTE(wc
)) {
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
;
205 self
->qf_currq
.s
[self
->qf_currq
.l
++] = buf
[-1];
209 /* The quote is parsed and compressed; dump it */
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
);
225 _qf_state_data(struct qf_vc
*vc
)
227 struct quoteflt
*self
= vc
->self
;
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];
243 self
->qf_mbps
[1] = self
->qf_mbps
[0];
244 if (i
== (size_t)-2) {
245 /* Redundant shift sequence, out of buffer */
252 { ssize_t j
= _qf_add_data(self
, wc
);
260 if (self
->qf_state
!= _QF_DATA
)
268 #endif /* HAVE_QUOTE_FOLD */
271 quoteflt_dummy(void) /* TODO LEGACY */
273 static struct quoteflt qf_i
;
279 quoteflt_init(struct quoteflt
*self
, char const *prefix
)
281 #ifdef HAVE_QUOTE_FOLD
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);
302 qmin
= (ui_it
)strtol(xcp
+ 1, NULL
, 10);
303 if (qmin
< qmax
>> 1)
305 else if (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
);
319 quoteflt_destroy(struct quoteflt
*self
) /* xxx inline */
325 quoteflt_reset(struct quoteflt
*self
, FILE *f
) /* xxx inline */
328 #ifdef HAVE_QUOTE_FOLD
329 self
->qf_state
= _QF_CLEAN
;
331 self
->qf_currq
.l
= 0;
332 memset(self
->qf_mbps
, 0, sizeof self
->qf_mbps
);
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 ..) */
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 */
351 #ifdef HAVE_QUOTE_FOLD
352 if (self
->qf_qfold_max
== 0)
357 bool_t pxok
= (self
->qf_qfold_min
!= 0);
361 i
= fwrite(self
->qf_pfix
, sizeof *self
->qf_pfix
, self
->qf_pfix_len
,
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
)
378 ll
= (size_t)((char*)vp
- dat
) + 1;
381 i
= fwrite(dat
, sizeof *dat
, ll
, self
->qf_os
);
385 if ((len
-= ll
) == 0)
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
411 switch (self
->qf_state
) {
414 i
= _qf_state_prefix(&vc
);
416 default: /* silence cc (`i' unused) */
418 i
= _qf_state_data(&vc
);
426 #endif /* HAVE_QUOTE_FOLD */
437 quoteflt_flush(struct quoteflt
*self
)
442 #ifdef HAVE_QUOTE_FOLD
443 if (self
->qf_dat
.l
> 0) {
444 rv
= _qf_dump_prefix(self
);
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
;
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
;
459 /* vim:set fenc=utf-8:s-it-mode */