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.
27 #undef HAVE_QUOTE_FOLD
29 #ifdef HAVE_QUOTE_FOLD
30 CTASSERT(QUOTE_MAX
> 3);
33 _QF_NONE
, /* Clean line, nothing read yet */
34 _QF_PREFIX
, /* Still collecting prefix */
35 _QF_DATA
/* Prefix passed */
39 struct quoteflt
* self
;
44 static ssize_t
_qf_state_prefix(struct qf_vc
*vc
);
45 static ssize_t
_qf_state_data(struct qf_vc
*vc
);
49 prefixwrite(char const *ptr
, size_t size
, FILE *f
,
50 char const *prefix
, size_t prefixlen
)
56 /* After writing a real newline followed by our prefix,
57 * compress the quoted prefixes;
58 * note that \n is only matched by spacechar(), not by
59 * blankchar() or blankspacechar() */
61 * TODO the problem we have is again the odd 4:3 relation of
62 * TODO base64 -- if we quote a mail that is in base64 then
63 * TODO prefixwrite() doesn't get invoked with partial multi-
64 * TODO byte characters (S-nail uses the `rest' mechanism to
65 * TODO avoid that), but it may of course be invoked with a
66 * TODO partial line, and even with multiple thereof.
67 * TODO this has to be addressed in 15.0 with the MIME and send
68 * TODO layer rewrite. The solution is that `prefixwrite' has
69 * TODO to be an object with state -- if a part is to be quoted
70 * TODO you create it, and simply feed in data; once the part
71 * TODO is done, you'll release it; the object itself gobbles
72 * TODO data unless a *hard* newline is seen.
73 * TODO in fact we can then even implement paragraph-wise
74 * TODO quoting! FIXME in fact that is the way: objects
75 * TODO then, evaluate quote-fold when sending starts, ONCE!
76 * FIXME NOTE: base64 (yet TODO for qp) may have CRLF line
77 * FIXME endings, these need to be removed in a LOWER LAYER!!
79 fprintf(stderr
, "ENTRY: lastlen=%zu, size=%zu <%.*s>\n",lastlen
,size
,(int)size
,p
);
80 if ((lnlen
= lastlen
) != 0)
84 for (zipl
= i
= 0; p
+ i
< maxp
; ++i
) {
86 if (blankspacechar(c
))
90 if (zipl
== sizeof(zipb
) - 1) {
91 zipb
[sizeof(zipb
) - 2] = '.';
92 zipb
[sizeof(zipb
) - 3] = '.';
93 zipb
[sizeof(zipb
) - 4] = '.';
102 wsz
+= fwrite(zipb
, sizeof *zipb
, zipl
, f
);
107 /* Search forward until either *quote-fold* or NL.
108 * In the former case try to break at whitespace,
109 * but only if's located in the 2nd half of the data */
110 for (c
= i
= 0; p
+ i
< maxp
;) {
114 if (lnlen
+ i
<= qfold_max
)
117 /* We're excessing bounds -- but don't "continue"
118 * trailing WS nor a continuation */
119 if (c
== '\\' || spacechar(c
)) {
122 for (cp
= p
+ i
; cp
< maxp
; ++cp
)
123 if (! spacechar(*cp
))
125 if (cp
== maxp
|| (*cp
== '\\' &&
127 i
= (size_t)(maxp
- p
);
131 } else if (p
+ i
< maxp
&& p
[i
] == '\n') {
137 /* We have to fold this line */
138 if (qfold_min
< lnlen
) {
139 /* This is because of base64 and odd 4:3.
140 * i.e., entered with some partial line yet
141 * written.. This is weird, as we may have
142 * written out `qfold_max' already.. */
145 fprintf(stderr
, "-- size=%zu p<%.*s>\n",size
,(int)size
,p
);
147 j
= qfold_min
- lnlen
;
149 size
= j
+ ((qfold_max
- qfold_min
) >> 1);
150 assert(p
+ i
< maxp
);
152 while (i
> j
&& ! spacechar(p
[i
- 1]))
161 wsz
+= fwrite(p
, sizeof *p
, i
, f
);
172 wsz
+= fwrite(prefix
, sizeof *prefix
, prefixlen
, f
);
187 _qf_state_prefix(struct qf_vc
*vc
)
189 struct quoteflt
*self
= vc
->self
;
190 char const *buf
= vc
->buf
;
191 size_t len
= vc
->len
, i
;
198 if (len
< mb_cur_max
) {
200 memcpy(self
->dat
.s
+ self
->dat
.l
, vc
->buf
, len
);
205 i
= mbrtowc(&wc
, buf
, len
, &self
->mbps
);
206 if (i
== (size_t)-1 || i
== (size_t)-2)
215 if (i
== 1 && ISQUOTE(wc
)) {
216 if (self
->currq
.l
== QUOTE_MAX
- 3) {
217 self
->currq
.s
[QUOTE_MAX
- 3] = '.';
218 self
->currq
.s
[QUOTE_MAX
- 2] = '.';
219 self
->currq
.s
[QUOTE_MAX
- 1] = '.';
221 self
->currq
.s
[self
->currq
.l
++] = buf
[-1];
225 self
->qf_state
= _QF_DATA
;
238 _qf_state_data(struct qf_vc
*vc
)
246 #endif /* HAVE_QUOTE_FOLD */
249 quoteflt_dummy(void) /* TODO LEGACY */
251 static struct quoteflt qf_i
;
257 quoteflt_init(struct quoteflt
*self
, char const *prefix
)
259 #ifdef HAVE_QUOTE_FOLD
263 memset(self
, 0, sizeof *self
);
265 if ((self
->qf_pfix
= prefix
) != NULL
)
266 self
->qf_pfix_len
= (ui_it
)strlen(prefix
);
268 /* Check wether the user wants the more fancy quoting algorithm */
269 #ifdef HAVE_QUOTE_FOLD
270 if ((cp
= voption("quote-fold")) != NULL
) {
271 ui_it qmin
, qmax
= (ui_it
)strtol(cp
, (char**)&xcp
, 10);
272 if (qmax
< self
->qf_pfix_len
+ 6)
273 qmax
= self
->qf_pfix_len
+ 6;
274 --qmax
; /* The newline escape */
275 if (cp
== xcp
|| *xcp
== '\0')
276 qmin
= (qmax
>> 1) + (qmax
>> 2) + (qmax
>> 5);
278 qmin
= (ui_it
)strtol(xcp
+ 1, NULL
, 10);
279 if (qmin
< qmax
>> 1)
281 else if (qmin
> qmax
- 2)
284 self
->qf_qfold_min
= qmin
;
285 self
->qf_qfold_max
= qmax
;
287 self
->qf_dat
.s
= salloc((qmax
+ 2) * mb_cur_len
);
288 self
->qf_currq
.s
= salloc((QUOTE_MAX
+ 1) * mb_cur_len
);
294 quoteflt_destroy(struct quoteflt
*self
)
296 #ifndef HAVE_QUOTE_FOLD
301 if ((p
= self
->qf_dat
.s
) != NULL
)
303 if ((p
= self
->qf_currq
.s
) != NULL
)
309 quoteflt_reset(struct quoteflt
*self
, FILE *f
) /* XXX inline */
312 #ifdef HAVE_QUOTE_FOLD
313 self
->qf_state
= _QF_NONE
;
315 self
->qf_currq
.l
= 0;
316 memset(self
->qf_mbps
, 0, sizeof self
->qf_mbps
);
321 quoteflt_push(struct quoteflt
*self
, char const *dat
, size_t len
)
323 /* (xxx Ideally the actual push() [and flush()] would be functions on their
324 * xxx own, via indirect vtbl call ..) */
330 /* Simple bypass? XXX Finally, this filter simply should not be used, then */
331 if (self
->qf_pfix_len
== 0)
332 rv
= fwrite(dat
, sizeof *dat
, len
, self
->qf_os
);
333 /* The simple algorithm: place *indentprefix* at every BOL */
335 #ifdef HAVE_QUOTE_FOLD
336 if (self
->qf_qfold_max
== 0)
341 bool_t pxok
= (self
->qf_qfold_min
!= 0);
345 i
= fwrite(self
->qf_pfix
, sizeof *self
->qf_pfix
, self
->qf_pfix_len
,
353 /* xxx Strictly speaking this is invalid, because only `/' and `.' are
354 * xxx mandated by POSIX.1-2008 as "invariant across all locales
355 * xxx supported"; though there is no charset known which uses this
356 * xxx control char as part of a multibyte character; note that S-nail
357 * XXX (and the Mail codebase as such) do not support EBCDIC */
358 if ((vp
= memchr(dat
, '\n', len
)) == NULL
)
362 ll
= (size_t)((char*)vp
- dat
) + 1;
365 i
= fwrite(dat
, sizeof *dat
, ll
, self
->qf_os
);
369 if ((len
-= ll
) == 0)
374 self
->qf_qfold_min
= pxok
;
376 /* More complicated, though still only line-per-line: *quote-fold*.
377 * - If .qf_currq.l is 0, then we are in a clean state. Reset .qf_mbps;
378 * note this means we assume that lines start with reset escape seq
379 * - Lookout for a newline
381 #ifdef HAVE_QUOTE_FOLD
390 switch (self
->qf_state
) {
393 i
= _qf_state_prefix(&vc
);
396 i
= _qf_state_data(&vc
);
405 #endif /* HAVE_QUOTE_FOLD */
416 quoteflt_flush(struct quoteflt
*self
)
421 #ifdef HAVE_QUOTE_FOLD
422 if (self
->qf_dat
.l
> 0) {
423 rv
= fwrite(self
->qf_pfix
, sizeof *self
->qf_pfix
, self
->qf_pfix_len
,
426 ssize_t j
= fwrite(self
->qf_dat
.s
, sizeof *self
->qf_dat
.s
,
427 self
->qf_dat
.l
, self
->qf_os
);
428 rv
= (j
< 0) ? j
: rv
+ j
;
436 /* vim:set fenc=utf-8:s-it-mode */