quoteflt_dummy() usage: FIX usage without preceeding _reset() (Jürgen Bruckner)
[s-mailx.git] / mime.c
blob5619a78ae1196afb0afbdcc919705977c1d9c85c
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ MIME support functions.
3 *@ TODO Complete rewrite.
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 */
8 /*
9 * Copyright (c) 2000
10 * Gunnar Ritter. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. All advertising materials mentioning features or use of this software
21 * must display the following acknowledgement:
22 * This product includes software developed by Gunnar Ritter
23 * and his contributors.
24 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
25 * may be used to endorse or promote products derived from this software
26 * without specific prior written permission.
28 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * SUCH DAMAGE.
40 #undef n_FILE
41 #define n_FILE mime
43 #ifndef HAVE_AMALGAMATION
44 # include "nail.h"
45 #endif
47 /* Don't ask, but it keeps body and soul together */
48 enum a_mime_structure_hack{
49 a_MIME_SH_NONE,
50 a_MIME_SH_COMMENT,
51 a_MIME_SH_QUOTE
54 static char *_cs_iter_base, *_cs_iter;
55 #ifdef HAVE_ICONV
56 # define _CS_ITER_GET() \
57 ((_cs_iter != NULL) ? _cs_iter : ok_vlook(CHARSET_8BIT_OKEY))
58 #else
59 # define _CS_ITER_GET() ((_cs_iter != NULL) ? _cs_iter : ok_vlook(ttycharset))
60 #endif
61 #define _CS_ITER_STEP() _cs_iter = n_strsep(&_cs_iter_base, ',', TRU1)
63 /* Is 7-bit enough? */
64 #ifdef HAVE_ICONV
65 static bool_t _has_highbit(char const *s);
66 static bool_t _name_highbit(struct name *np);
67 #endif
69 /* fwrite(3) while checking for displayability */
70 static ssize_t _fwrite_td(struct str const *input,
71 bool_t failiconv, enum tdflags flags,
72 struct str *outrest, struct quoteflt *qf);
74 /* Convert header fields to RFC 2047 format and write to the file fo */
75 static ssize_t mime_write_tohdr(struct str *in, FILE *fo,
76 size_t *colp, enum a_mime_structure_hack msh);
78 #ifdef HAVE_ICONV
79 static ssize_t a_mime__convhdra(struct str *inp, FILE *fp, size_t *colp,
80 enum a_mime_structure_hack msh);
81 #else
82 # define a_mime__convhdra(S,F,C,MSH) mime_write_tohdr(S, F, C, MSH)
83 #endif
85 /* Write an address to a header field */
86 static ssize_t mime_write_tohdr_a(struct str *in, FILE *f,
87 size_t *colp, enum a_mime_structure_hack msh);
89 /* Append to buf, handling resizing */
90 static void _append_str(char **buf, size_t *sz, size_t *pos,
91 char const *str, size_t len);
92 static void _append_conv(char **buf, size_t *sz, size_t *pos,
93 char const *str, size_t len);
95 #ifdef HAVE_ICONV
96 static bool_t
97 _has_highbit(char const *s)
99 bool_t rv = TRU1;
100 NYD_ENTER;
102 if (s) {
104 if ((ui8_t)*s & 0x80)
105 goto jleave;
106 while (*s++ != '\0');
108 rv = FAL0;
109 jleave:
110 NYD_LEAVE;
111 return rv;
114 static bool_t
115 _name_highbit(struct name *np)
117 bool_t rv = TRU1;
118 NYD_ENTER;
120 while (np) {
121 if (_has_highbit(np->n_name) || _has_highbit(np->n_fullname))
122 goto jleave;
123 np = np->n_flink;
125 rv = FAL0;
126 jleave:
127 NYD_LEAVE;
128 return rv;
130 #endif /* HAVE_ICONV */
132 static sigjmp_buf __mimefwtd_actjmp; /* TODO someday.. */
133 static int __mimefwtd_sig; /* TODO someday.. */
134 static sighandler_type __mimefwtd_opipe;
135 static void
136 __mimefwtd_onsig(int sig) /* TODO someday, we won't need it no more */
138 NYD_X; /* Signal handler */
139 __mimefwtd_sig = sig;
140 siglongjmp(__mimefwtd_actjmp, 1);
143 static ssize_t
144 _fwrite_td(struct str const *input, bool_t failiconv, enum tdflags flags,
145 struct str *outrest, struct quoteflt *qf)
147 /* TODO note: after send/MIME layer rewrite we will have a string pool
148 * TODO so that memory allocation count drops down massively; for now,
149 * TODO v14.* that is, we pay a lot & heavily depend on the allocator */
150 /* TODO well if we get a broken pipe here, and it happens to
151 * TODO happen pretty easy when sleeping in a full pipe buffer,
152 * TODO then the current codebase performs longjump away;
153 * TODO this leaves memory leaks behind ('think up to 3 per,
154 * TODO dep. upon alloca availability). For this to be fixed
155 * TODO we either need to get rid of the longjmp()s (tm) or
156 * TODO the storage must come from the outside or be tracked
157 * TODO in a carrier struct. Best both. But storage reuse
158 * TODO would be a bigbig win besides */
159 /* *input* _may_ point to non-modifyable buffer; but even then it only
160 * needs to be dup'ed away if we have to transform the content */
161 struct str in, out;
162 ssize_t rv;
163 NYD_ENTER;
164 n_UNUSED(failiconv);
165 n_UNUSED(outrest);
167 in = *input;
168 out.s = NULL;
169 out.l = 0;
171 #ifdef HAVE_ICONV
172 if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
173 int err;
174 char *buf;
176 buf = NULL;
178 if (outrest != NULL && outrest->l > 0) {
179 in.l = outrest->l + input->l;
180 in.s = buf = smalloc(in.l +1);
181 memcpy(in.s, outrest->s, outrest->l);
182 memcpy(&in.s[outrest->l], input->s, input->l);
183 outrest->l = 0;
186 rv = 0;
188 /* TODO Sigh, no problem if we have a filter that has a buffer (or
189 * TODO become fed with entire lines, whatever), but for now we need
190 * TODO to ensure we pass entire lines from in here to iconv(3), because
191 * TODO the Citrus iconv(3) will fail tests with stateful encodings
192 * TODO if we do not (only seen on FreeBSD) */
193 #if 0 /* TODO actually not needed indeed, it was known iswprint() error! */
194 if(!(flags & _TD_EOF) && outrest != NULL){
195 size_t i, j;
196 char const *cp;
198 if((cp = memchr(in.s, '\n', j = in.l)) != NULL){
199 i = PTR2SIZE(cp - in.s);
200 j -= i;
201 while(j > 0 && *cp == '\n') /* XXX one iteration too much */
202 ++cp, --j, ++i;
203 if(j != 0)
204 n_str_assign_buf(outrest, cp, j);
205 in.l = i;
206 }else{
207 n_str_assign(outrest, &in);
208 goto jleave;
211 #endif
213 if((err = n_iconv_str(iconvd,
214 (failiconv ? n_ICONV_NONE : n_ICONV_UNIDEFAULT),
215 &out, &in, &in)) != 0){
216 if(err != n_ERR_INVAL)
217 n_iconv_reset(iconvd);
219 if(outrest != NULL && in.l > 0){
220 /* Incomplete multibyte at EOF is special xxx _INVAL? */
221 if (flags & _TD_EOF) {
222 out.s = srealloc(out.s, out.l + sizeof(n_unirepl));
223 if(n_psonce & n_PSO_UNICODE){
224 memcpy(&out.s[out.l], n_unirepl, sizeof(n_unirepl) -1);
225 out.l += sizeof(n_unirepl) -1;
226 }else
227 out.s[out.l++] = '?';
228 } else
229 n_str_add(outrest, &in);
230 }else
231 rv = -1;
233 in = out;
234 out.l = 0;
235 out.s = NULL;
236 flags &= ~_TD_BUFCOPY;
238 if(buf != NULL)
239 n_free(buf);
240 if(rv < 0)
241 goto jleave;
242 }else
243 #endif /* HAVE_ICONV */
244 /* Else, if we will modify the data bytes and thus introduce the potential
245 * of messing up multibyte sequences which become splitted over buffer
246 * boundaries TODO and unless we don't have our filter chain which will
247 * TODO make these hacks go by, buffer data until we see a NL */
248 if((flags & (TD_ISPR | TD_DELCTRL)) && outrest != NULL &&
249 #ifdef HAVE_ICONV
250 iconvd == (iconv_t)-1 &&
251 #endif
252 (!(flags & _TD_EOF) || outrest->l > 0)
254 size_t i;
255 char *cp;
257 for (cp = &in.s[in.l]; cp > in.s && cp[-1] != '\n'; --cp)
259 i = PTR2SIZE(cp - in.s);
261 if (i != in.l) {
262 if (i > 0) {
263 n_str_assign_buf(outrest, cp, in.l - i);
264 cp = smalloc(i +1);
265 memcpy(cp, in.s, in.l = i);
266 (in.s = cp)[in.l = i] = '\0';
267 flags &= ~_TD_BUFCOPY;
268 } else {
269 n_str_add_buf(outrest, input->s, input->l);
270 rv = 0;
271 goto jleave;
276 if (flags & TD_ISPR)
277 makeprint(&in, &out);
278 else if (flags & _TD_BUFCOPY)
279 n_str_dup(&out, &in);
280 else
281 out = in;
282 if (flags & TD_DELCTRL)
283 out.l = delctrl(out.s, out.l);
285 __mimefwtd_sig = 0;
286 __mimefwtd_opipe = safe_signal(SIGPIPE, &__mimefwtd_onsig);
287 if (sigsetjmp(__mimefwtd_actjmp, 1)) {
288 rv = 0;
289 goto j__sig;
292 rv = quoteflt_push(qf, out.s, out.l);
294 j__sig:
295 if (out.s != in.s)
296 n_free(out.s);
297 if (in.s != input->s)
298 n_free(in.s);
299 safe_signal(SIGPIPE, __mimefwtd_opipe);
300 if (__mimefwtd_sig != 0)
301 n_raise(__mimefwtd_sig);
302 jleave:
303 NYD_LEAVE;
304 return rv;
307 static ssize_t
308 mime_write_tohdr(struct str *in, FILE *fo, size_t *colp,
309 enum a_mime_structure_hack msh)
311 /* TODO mime_write_tohdr(): we don't know the name of our header->maxcol..
312 * TODO MIME/send layer rewrite: more available state!!
313 * TODO Because of this we cannot make a difference in between structured
314 * TODO and unstructured headers (RFC 2047, 5. (2))
315 * TODO This means, e.g., that this gets called multiple times for a
316 * TODO structured header and always starts thinking it is at column 0.
317 * TODO I.e., it may get called for only the content of a comment etc.,
318 * TODO not knowing anything of its context.
319 * TODO Instead we should have a list of header body content tokens,
320 * TODO convert them, and then dump the converted tokens, breaking lines.
321 * TODO I.e., get rid of convhdra, mime_write_tohdr_a and such...
322 * TODO Somewhen, the following should produce smooth stuff:
323 * TODO ' "Hallo\"," Dr. Backe "Bl\"ö\"d" (Gell) <ha@llöch.en>
324 * TODO "Nochm\"a\"l"<ta@tu.da>(Dümm)'
325 * TODO NOT MULTIBYTE SAFE IF AN ENCODED WORD HAS TO BE SPLITTED!
326 * TODO To be better we had to mbtowc_l() (non-std! and no locale!!) and
327 * TODO work char-wise! -> S-CText..
328 * TODO The real problem for STD compatibility is however that "in" is
329 * TODO already iconv(3) encoded to the target character set! We could
330 * TODO also solve it (very expensively!) if we would narrow down to an
331 * TODO encoded word and then iconv(3)+MIME encode in one go, in which
332 * TODO case multibyte errors could be catched! */
333 enum {
334 /* Maximum line length */
335 a_MAXCOL_NENC = MIME_LINELEN,
336 a_MAXCOL = MIME_LINELEN_RFC2047
339 struct str cout, cin;
340 enum {
341 _FIRST = 1<<0, /* Nothing written yet, start of string */
342 _MSH_NOTHING = 1<<1, /* Now, really: nothing at all has been written */
343 a_ANYENC = 1<<2, /* We have RFC 2047 anything at least once */
344 _NO_QP = 1<<3, /* No quoted-printable allowed */
345 _NO_B64 = 1<<4, /* Ditto, base64 */
346 _ENC_LAST = 1<<5, /* Last round generated encoded word */
347 _SHOULD_BEE = 1<<6, /* Avoid lines longer than SHOULD via encoding */
348 _RND_SHIFT = 7,
349 _RND_MASK = (1<<_RND_SHIFT) - 1,
350 _SPACE = 1<<(_RND_SHIFT+1), /* Leading whitespace */
351 _8BIT = 1<<(_RND_SHIFT+2), /* High bit set */
352 _ENCODE = 1<<(_RND_SHIFT+3), /* Need encoding */
353 _ENC_B64 = 1<<(_RND_SHIFT+4), /* - let it be base64 */
354 _OVERLONG = 1<<(_RND_SHIFT+5) /* Temporarily rised limit */
355 } flags;
356 char const *cset7, *cset8, *wbot, *upper, *wend, *wcur;
357 ui32_t cset7_len, cset8_len;
358 size_t col, i, j;
359 ssize_t sz;
361 NYD_ENTER;
363 cout.s = NULL, cout.l = 0;
364 cset7 = ok_vlook(charset_7bit);
365 cset7_len = (ui32_t)strlen(cset7);
366 cset8 = _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */
367 cset8_len = (ui32_t)strlen(cset8);
369 flags = _FIRST;
370 if(msh != a_MIME_SH_NONE)
371 flags |= _MSH_NOTHING;
373 /* RFC 1468, "MIME Considerations":
374 * ISO-2022-JP may also be used in MIME Part 2 headers. The "B"
375 * encoding should be used with ISO-2022-JP text. */
376 /* TODO of course, our current implementation won't deal properly with
377 * TODO any stateful encoding at all... (the standard says each encoded
378 * TODO word must include all necessary reset sequences..., i.e., each
379 * TODO encoded word must be a self-contained iconv(3) life cycle) */
380 if (!asccasecmp(cset8, "iso-2022-jp") || mime_enc_target() == MIMEE_B64)
381 flags |= _NO_QP;
383 wbot = in->s;
384 upper = wbot + in->l;
385 sz = 0;
387 if(colp == NULL || (col = *colp) == 0)
388 col = sizeof("Mail-Followup-To: ") -1; /* TODO dreadful thing */
390 /* The user may specify empy quoted-strings or comments, keep them! */
391 if(wbot == upper) {
392 if(flags & _MSH_NOTHING){
393 flags &= ~_MSH_NOTHING;
394 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
395 sz = 1;
396 ++col;
398 } else for (; wbot < upper; flags &= ~_FIRST, wbot = wend) {
399 flags &= _RND_MASK;
400 wcur = wbot;
401 while (wcur < upper && whitechar(*wcur)) {
402 flags |= _SPACE;
403 ++wcur;
406 /* Any occurrence of whitespace resets prevention of lines >SHOULD via
407 * enforced encoding (xxx SHOULD, but.. encoding is expensive!!) */
408 if (flags & _SPACE)
409 flags &= ~_SHOULD_BEE;
411 /* Data ends with WS - dump it and done.
412 * Also, if we have seen multiple successive whitespace characters, then
413 * if there was no encoded word last, i.e., if we can simply take them
414 * over to the output as-is, keep one WS for possible later separation
415 * purposes and simply print the others as-is, directly! */
416 if (wcur == upper) {
417 wend = wcur;
418 goto jnoenc_putws;
420 if ((flags & (_ENC_LAST | _SPACE)) == _SPACE && wcur - wbot > 1) {
421 wend = wcur - 1;
422 goto jnoenc_putws;
425 /* Skip over a word to next non-whitespace, keep track along the way
426 * whether our 7-bit charset suffices to represent the data */
427 for (wend = wcur; wend < upper; ++wend) {
428 if (whitechar(*wend))
429 break;
430 if ((uc_i)*wend & 0x80)
431 flags |= _8BIT;
434 /* Decide whether the range has to become encoded or not */
435 i = PTR2SIZE(wend - wcur);
436 j = mime_enc_mustquote(wcur, i, MIMEEF_ISHEAD);
437 /* If it just cannot fit on a SHOULD line length, force encode */
438 if (i > a_MAXCOL_NENC) {
439 flags |= _SHOULD_BEE; /* (Sigh: SHOULD only, not MUST..) */
440 goto j_beejump;
442 if ((flags & _SHOULD_BEE) || j > 0) {
443 j_beejump:
444 flags |= _ENCODE;
445 /* Use base64 if requested or more than 50% -37.5-% of the bytes of
446 * the string need to be encoded */
447 if ((flags & _NO_QP) || j >= i >> 1)/*(i >> 2) + (i >> 3))*/
448 flags |= _ENC_B64;
450 DBG( if (flags & _8BIT) assert(flags & _ENCODE); )
452 if (!(flags & _ENCODE)) {
453 /* Encoded word produced, but no linear whitespace for necessary RFC
454 * 2047 separation? Generate artificial data (bad standard!) */
455 if ((flags & (_ENC_LAST | _SPACE)) == _ENC_LAST) {
456 if (col >= a_MAXCOL) {
457 putc('\n', fo);
458 ++sz;
459 col = 0;
461 if(flags & _MSH_NOTHING){
462 flags &= ~_MSH_NOTHING;
463 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
464 ++sz;
465 ++col;
467 putc(' ', fo);
468 ++sz;
469 ++col;
472 jnoenc_putws:
473 flags &= ~_ENC_LAST;
475 /* todo No effort here: (1) v15.0 has to bring complete rewrite,
476 * todo (2) the standard is braindead and (3) usually this is one
477 * todo word only, and why be smarter than the standard? */
478 jnoenc_retry:
479 i = PTR2SIZE(wend - wbot);
480 if (i + col + ((flags & _MSH_NOTHING) != 0) <=
481 (flags & _OVERLONG ? MIME_LINELEN_MAX
482 : (flags & a_ANYENC ? a_MAXCOL : a_MAXCOL_NENC))) {
483 if(flags & _MSH_NOTHING){
484 flags &= ~_MSH_NOTHING;
485 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
486 ++sz;
487 ++col;
489 i = fwrite(wbot, sizeof *wbot, i, fo);
490 sz += i;
491 col += i;
492 continue;
495 /* Doesn't fit, try to break the line first; */
496 if (col > 1) {
497 putc('\n', fo);
498 if (whitechar(*wbot)) {
499 putc((uc_i)*wbot, fo);
500 ++wbot;
501 } else
502 putc(' ', fo); /* Bad standard: artificial data! */
503 sz += 2;
504 col = 1;
505 if(flags & _MSH_NOTHING){
506 flags &= ~_MSH_NOTHING;
507 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
508 ++sz;
509 ++col;
511 flags |= _OVERLONG;
512 goto jnoenc_retry;
515 /* It is so long that it needs to be broken, effectively causing
516 * artificial spaces to be inserted (bad standard), yuck */
517 /* todo This is not multibyte safe, as above; and completely stupid
518 * todo P.S.: our _SHOULD_BEE prevents these cases in the meanwhile */
519 /* FIXME n_PSO_UNICODE and parse using UTF-8 sync possibility! */
520 wcur = wbot + MIME_LINELEN_MAX - 8;
521 while (wend > wcur)
522 wend -= 4;
523 goto jnoenc_retry;
524 } else {
525 /* Encoding to encoded word(s); deal with leading whitespace, place
526 * a separator first as necessary: encoded words must always be
527 * separated from text and other encoded words with linear WS.
528 * And if an encoded word was last, intermediate whitespace must
529 * also be encoded, otherwise it would get stripped away! */
530 wcur = n_UNCONST(n_empty);
531 if ((flags & (_ENC_LAST | _SPACE)) != _SPACE) {
532 /* Reinclude whitespace */
533 flags &= ~_SPACE;
534 /* We don't need to place a separator at the very beginning */
535 if (!(flags & _FIRST))
536 wcur = n_UNCONST(" ");
537 } else
538 wcur = wbot++;
540 flags |= a_ANYENC | _ENC_LAST;
541 n_pstate |= n_PS_HEADER_NEEDED_MIME;
543 /* RFC 2047:
544 * An 'encoded-word' may not be more than 75 characters long,
545 * including 'charset', 'encoding', 'encoded-text', and
546 * delimiters. If it is desirable to encode more text than will
547 * fit in an 'encoded-word' of 75 characters, multiple
548 * 'encoded-word's (separated by CRLF SPACE) may be used.
550 * While there is no limit to the length of a multiple-line
551 * header field, each line of a header field that contains one
552 * or more 'encoded-word's is limited to 76 characters */
553 jenc_retry:
554 cin.s = n_UNCONST(wbot);
555 cin.l = PTR2SIZE(wend - wbot);
557 /* C99 */{
558 struct str *xout;
560 if(flags & _ENC_B64)
561 xout = b64_encode(&cout, &cin, B64_ISHEAD | B64_ISENCWORD);
562 else
563 xout = qp_encode(&cout, &cin, QP_ISHEAD | QP_ISENCWORD);
564 if(xout == NULL){
565 sz = -1;
566 break;
568 j = xout->l;
570 /* (Avoid trigraphs in the RFC 2047 placeholder..) */
571 i = j + (flags & _8BIT ? cset8_len : cset7_len) + sizeof("=!!B!!=") -1;
572 if (*wcur != '\0')
573 ++i;
575 jenc_retry_same:
576 /* Unfortunately RFC 2047 explicitly disallows encoded words to be
577 * longer (just like RFC 5322's "a line SHOULD fit in 78 but MAY be
578 * 998 characters long"), so we cannot use the _OVERLONG mechanism,
579 * even though all tested mailers seem to support it */
580 if (i + col <= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ a_MAXCOL)) {
581 if(flags & _MSH_NOTHING){
582 flags &= ~_MSH_NOTHING;
583 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
584 ++sz;
585 ++col;
587 fprintf(fo, "%.1s=?%s?%c?%.*s?=",
588 wcur, (flags & _8BIT ? cset8 : cset7),
589 (flags & _ENC_B64 ? 'B' : 'Q'),
590 (int)cout.l, cout.s);
591 sz += i;
592 col += i;
593 continue;
596 /* Doesn't fit, try to break the line first */
597 /* TODO I've commented out the _FIRST test since we (1) cannot do
598 * TODO _OVERLONG since (MUAs support but) the standard disallows,
599 * TODO and because of our iconv problem i prefer an empty first line
600 * TODO in favour of a possibly messed up multibytes character. :-( */
601 if (col > 1 /* TODO && !(flags & _FIRST)*/) {
602 putc('\n', fo);
603 sz += 2;
604 col = 1;
605 if (!(flags & _SPACE)) {
606 putc(' ', fo);
607 wcur = n_UNCONST(n_empty);
608 /*flags |= _OVERLONG;*/
609 goto jenc_retry_same;
610 } else {
611 putc((uc_i)*wcur, fo);
612 if (whitechar(*(wcur = wbot)))
613 ++wbot;
614 else {
615 flags &= ~_SPACE;
616 wcur = n_UNCONST(n_empty);
618 /*flags &= ~_OVERLONG;*/
619 goto jenc_retry;
623 /* It is so long that it needs to be broken, effectively causing
624 * artificial data to be inserted (bad standard), yuck */
625 /* todo This is not multibyte safe, as above */
626 /*if (!(flags & _OVERLONG)) { Mechanism explicitly forbidden by 2047
627 flags |= _OVERLONG;
628 goto jenc_retry;
631 /* FIXME n_PSO_UNICODE and parse using UTF-8 sync possibility! */
632 i = PTR2SIZE(wend - wbot) + !!(flags & _SPACE);
633 j = 3 + !(flags & _ENC_B64);
634 for (;;) {
635 wend -= j;
636 i -= j;
637 /* (Note the problem most likely is the transfer-encoding blow,
638 * which is why we test this *after* the decrements.. */
639 if (i <= a_MAXCOL)
640 break;
642 goto jenc_retry;
646 if(!(flags & _MSH_NOTHING) && msh != a_MIME_SH_NONE){
647 putc((msh == a_MIME_SH_COMMENT ? ')' : '"'), fo);
648 ++sz;
649 ++col;
652 if(cout.s != NULL)
653 n_free(cout.s);
655 if(colp != NULL)
656 *colp = col;
657 NYD_LEAVE;
658 return sz;
661 #ifdef HAVE_ICONV
662 static ssize_t
663 a_mime__convhdra(struct str *inp, FILE *fp, size_t *colp,
664 enum a_mime_structure_hack msh){
665 struct str ciconv;
666 ssize_t rv;
667 NYD_ENTER;
669 rv = 0;
670 ciconv.s = NULL;
672 if(inp->l > 0 && iconvd != (iconv_t)-1){
673 ciconv.l = 0;
674 if(n_iconv_str(iconvd, n_ICONV_NONE, &ciconv, inp, NULL) != 0){
675 n_iconv_reset(iconvd);
676 goto jleave;
678 *inp = ciconv;
681 rv = mime_write_tohdr(inp, fp, colp, msh);
682 jleave:
683 if(ciconv.s != NULL)
684 n_free(ciconv.s);
685 NYD_LEAVE;
686 return rv;
688 #endif /* HAVE_ICONV */
690 static ssize_t
691 mime_write_tohdr_a(struct str *in, FILE *f, size_t *colp,
692 enum a_mime_structure_hack msh)
694 struct str xin;
695 size_t i;
696 char const *cp, *lastcp;
697 ssize_t sz, x;
698 NYD_ENTER;
700 in->s[in->l] = '\0';
702 if((cp = routeaddr(lastcp = in->s)) != NULL && cp > lastcp) {
703 xin.s = n_UNCONST(lastcp);
704 xin.l = PTR2SIZE(cp - lastcp);
705 if ((sz = a_mime__convhdra(&xin, f, colp, msh)) < 0)
706 goto jleave;
707 lastcp = cp;
708 } else {
709 cp = lastcp;
710 sz = 0;
713 for( ; *cp != '\0'; ++cp){
714 switch(*cp){
715 case '(':
716 i = PTR2SIZE(cp - lastcp);
717 if(i > 0){
718 if(fwrite(lastcp, 1, i, f) != i)
719 goto jerr;
720 sz += i;
722 lastcp = ++cp;
723 cp = skip_comment(cp);
724 if(cp > lastcp)
725 --cp;
726 /* We want to keep empty comments, too! */
727 xin.s = n_UNCONST(lastcp);
728 xin.l = PTR2SIZE(cp - lastcp);
729 if ((x = a_mime__convhdra(&xin, f, colp, a_MIME_SH_COMMENT)) < 0)
730 goto jerr;
731 sz += x;
732 lastcp = &cp[1];
733 break;
734 case '"':
735 i = PTR2SIZE(cp - lastcp);
736 if(i > 0){
737 if(fwrite(lastcp, 1, i, f) != i)
738 goto jerr;
739 sz += i;
741 for(lastcp = ++cp; *cp != '\0'; ++cp){
742 if(*cp == '"')
743 break;
744 if(*cp == '\\' && cp[1] != '\0')
745 ++cp;
747 /* We want to keep empty quoted-strings, too! */
748 xin.s = n_UNCONST(lastcp);
749 xin.l = PTR2SIZE(cp - lastcp);
750 if((x = a_mime__convhdra(&xin, f, colp, a_MIME_SH_QUOTE)) < 0)
751 goto jerr;
752 sz += x;
753 ++sz;
754 lastcp = &cp[1];
755 break;
759 i = PTR2SIZE(cp - lastcp);
760 if(i > 0){
761 if(fwrite(lastcp, 1, i, f) != i)
762 goto jerr;
763 sz += i;
765 jleave:
766 NYD_LEAVE;
767 return sz;
768 jerr:
769 sz = -1;
770 goto jleave;
773 static void
774 _append_str(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
776 NYD_ENTER;
777 *buf = srealloc(*buf, *sz += len);
778 memcpy(&(*buf)[*pos], str, len);
779 *pos += len;
780 NYD_LEAVE;
783 static void
784 _append_conv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
786 struct str in, out;
787 NYD_ENTER;
789 in.s = n_UNCONST(str);
790 in.l = len;
791 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
792 _append_str(buf, sz, pos, out.s, out.l);
793 n_free(out.s);
794 NYD_LEAVE;
797 FL bool_t
798 charset_iter_reset(char const *a_charset_to_try_first) /* TODO elim. dups! */
800 char const *sarr[3];
801 size_t sarrl[3], len;
802 char *cp;
803 NYD_ENTER;
804 n_UNUSED(a_charset_to_try_first);
806 #ifdef HAVE_ICONV
807 sarr[2] = ok_vlook(CHARSET_8BIT_OKEY);
809 if(a_charset_to_try_first != NULL && strcmp(a_charset_to_try_first, sarr[2]))
810 sarr[0] = a_charset_to_try_first;
811 else
812 sarr[0] = NULL;
814 if((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
815 ok_blook(sendcharsets_else_ttycharset)){
816 cp = n_UNCONST(ok_vlook(ttycharset));
817 if(strcmp(cp, sarr[2]) && (sarr[0] == NULL || strcmp(cp, sarr[0])))
818 sarr[1] = cp;
820 #else
821 sarr[2] = ok_vlook(ttycharset);
822 #endif
824 sarrl[2] = len = strlen(sarr[2]);
825 #ifdef HAVE_ICONV
826 if ((cp = n_UNCONST(sarr[1])) != NULL)
827 len += (sarrl[1] = strlen(cp));
828 else
829 sarrl[1] = 0;
830 if ((cp = n_UNCONST(sarr[0])) != NULL)
831 len += (sarrl[0] = strlen(cp));
832 else
833 sarrl[0] = 0;
834 #endif
836 _cs_iter_base = cp = salloc(len + 1 + 1 +1);
838 #ifdef HAVE_ICONV
839 if ((len = sarrl[0]) != 0) {
840 memcpy(cp, sarr[0], len);
841 cp[len] = ',';
842 cp += ++len;
844 if ((len = sarrl[1]) != 0) {
845 memcpy(cp, sarr[1], len);
846 cp[len] = ',';
847 cp += ++len;
849 #endif
850 len = sarrl[2];
851 memcpy(cp, sarr[2], len);
852 cp[len] = '\0';
854 _CS_ITER_STEP();
855 NYD_LEAVE;
856 return (_cs_iter != NULL);
859 FL bool_t
860 charset_iter_next(void)
862 bool_t rv;
863 NYD_ENTER;
865 _CS_ITER_STEP();
866 rv = (_cs_iter != NULL);
867 NYD_LEAVE;
868 return rv;
871 FL bool_t
872 charset_iter_is_valid(void)
874 bool_t rv;
875 NYD_ENTER;
877 rv = (_cs_iter != NULL);
878 NYD_LEAVE;
879 return rv;
882 FL char const *
883 charset_iter(void)
885 char const *rv;
886 NYD_ENTER;
888 rv = _cs_iter;
889 NYD_LEAVE;
890 return rv;
893 FL char const *
894 charset_iter_or_fallback(void)
896 char const *rv;
897 NYD_ENTER;
899 rv = _CS_ITER_GET();
900 NYD_LEAVE;
901 return rv;
904 FL void
905 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
907 NYD_ENTER;
908 outer_storage[0] = _cs_iter_base;
909 outer_storage[1] = _cs_iter;
910 NYD_LEAVE;
913 FL void
914 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
916 NYD_ENTER;
917 _cs_iter_base = outer_storage[0];
918 _cs_iter = outer_storage[1];
919 NYD_LEAVE;
922 #ifdef HAVE_ICONV
923 FL char const *
924 need_hdrconv(struct header *hp) /* TODO once only, then iter */
926 struct n_header_field *hfp;
927 char const *rv;
928 NYD_ENTER;
930 rv = NULL;
932 /* C99 */{
933 struct n_header_field *chlp[3]; /* TODO JOINED AFTER COMPOSE! */
934 ui32_t i;
936 chlp[0] = n_poption_arg_C;
937 chlp[1] = n_customhdr_list;
938 chlp[2] = hp->h_user_headers;
940 for(i = 0; i < n_NELEM(chlp); ++i)
941 if((hfp = chlp[i]) != NULL)
942 do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
943 goto jneeds;
944 while((hfp = hfp->hf_next) != NULL);
947 if (hp->h_mft != NULL) {
948 if (_name_highbit(hp->h_mft))
949 goto jneeds;
951 if (hp->h_from != NULL) {
952 if (_name_highbit(hp->h_from))
953 goto jneeds;
954 } else if (_has_highbit(myaddrs(NULL)))
955 goto jneeds;
956 if (hp->h_reply_to) {
957 if (_name_highbit(hp->h_reply_to))
958 goto jneeds;
959 } else {
960 char const *v15compat;
962 if((v15compat = ok_vlook(replyto)) != NULL)
963 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
964 if(_has_highbit(v15compat))
965 goto jneeds;
966 if(_has_highbit(ok_vlook(reply_to)))
967 goto jneeds;
969 if (hp->h_sender) {
970 if (_name_highbit(hp->h_sender))
971 goto jneeds;
972 } else if (_has_highbit(ok_vlook(sender)))
973 goto jneeds;
975 if (_name_highbit(hp->h_to))
976 goto jneeds;
977 if (_name_highbit(hp->h_cc))
978 goto jneeds;
979 if (_name_highbit(hp->h_bcc))
980 goto jneeds;
981 if (_has_highbit(hp->h_subject))
982 jneeds:
983 rv = _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
984 NYD_LEAVE;
985 return rv;
987 #endif /* HAVE_ICONV */
989 FL void
990 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
992 /* TODO mime_fromhdr(): is called with strings that contain newlines;
993 * TODO this is the usual newline problem all around the codebase;
994 * TODO i.e., if we strip it, then the display misses it ;>
995 * TODO this is why it is so messy and why S-nail v14.2 plus additional
996 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
997 * TODO why our display reflects what is contained in the message: the 1:1
998 * TODO relationship of message content and display!
999 * TODO instead a header line should be decoded to what it is (a single
1000 * TODO line that is) and it should be objective to the backend whether
1001 * TODO it'll be folded to fit onto the display or not, e.g., for search
1002 * TODO purposes etc. then the only condition we have to honour in here
1003 * TODO is that whitespace in between multiple adjacent MIME encoded words
1004 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
1005 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
1006 struct str cin, cout;
1007 char *p, *op, *upper;
1008 ui32_t convert, lastenc, lastoutl;
1009 #ifdef HAVE_ICONV
1010 char const *tcs;
1011 char *cbeg;
1012 iconv_t fhicd = (iconv_t)-1;
1013 #endif
1014 NYD_ENTER;
1016 out->l = 0;
1017 if (in->l == 0) {
1018 *(out->s = smalloc(1)) = '\0';
1019 goto jleave;
1021 out->s = NULL;
1023 #ifdef HAVE_ICONV
1024 tcs = ok_vlook(ttycharset);
1025 #endif
1026 p = in->s;
1027 upper = p + in->l;
1028 lastenc = lastoutl = 0;
1030 while (p < upper) {
1031 op = p;
1032 if (*p == '=' && *(p + 1) == '?') {
1033 p += 2;
1034 #ifdef HAVE_ICONV
1035 cbeg = p;
1036 #endif
1037 while (p < upper && *p != '?')
1038 ++p; /* strip charset */
1039 if (p >= upper)
1040 goto jnotmime;
1041 ++p;
1042 #ifdef HAVE_ICONV
1043 if (flags & TD_ICONV) {
1044 size_t i = PTR2SIZE(p - cbeg);
1045 char *ltag, *cs = ac_alloc(i);
1047 memcpy(cs, cbeg, --i);
1048 cs[i] = '\0';
1049 /* RFC 2231 extends the RFC 2047 character set definition in
1050 * encoded words by language tags - silently strip those off */
1051 if ((ltag = strchr(cs, '*')) != NULL)
1052 *ltag = '\0';
1054 if (fhicd != (iconv_t)-1)
1055 n_iconv_close(fhicd);
1056 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
1057 ac_free(cs);
1059 #endif
1060 switch (*p) {
1061 case 'B': case 'b':
1062 convert = CONV_FROMB64;
1063 break;
1064 case 'Q': case 'q':
1065 convert = CONV_FROMQP;
1066 break;
1067 default: /* invalid, ignore */
1068 goto jnotmime;
1070 if (*++p != '?')
1071 goto jnotmime;
1072 cin.s = ++p;
1073 cin.l = 1;
1074 for (;;) {
1075 if (PTRCMP(p + 1, >=, upper))
1076 goto jnotmime;
1077 if (*p++ == '?' && *p == '=')
1078 break;
1079 ++cin.l;
1081 ++p;
1082 --cin.l;
1084 cout.s = NULL;
1085 cout.l = 0;
1086 if (convert == CONV_FROMB64) {
1087 if(!b64_decode_header(&cout, &cin))
1088 n_str_assign_cp(&cout, _("[Invalid Base64 encoding]"));
1089 }else if(!qp_decode_header(&cout, &cin))
1090 n_str_assign_cp(&cout, _("[Invalid Quoted-Printable encoding]"));
1091 /* Normalize all decoded newlines to spaces XXX only \0/\n yet */
1092 /* C99 */{
1093 char const *xcp;
1094 bool_t any;
1095 uiz_t i, j;
1097 for(any = FAL0, i = cout.l; i-- != 0;)
1098 switch(cout.s[i]){
1099 case '\0':
1100 case '\n':
1101 any = TRU1;
1102 cout.s[i] = ' ';
1103 /* FALLTHRU */
1104 default:
1105 break;
1109 if(any){
1110 /* I18N: must be non-empty, last must be closing bracket/xy */
1111 xcp = _("[Content normalized: ]");
1112 i = strlen(xcp);
1113 j = cout.l;
1114 n_str_add_buf(&cout, xcp, i);
1115 memmove(&cout.s[i - 1], cout.s, j);
1116 memcpy(&cout.s[0], xcp, i - 1);
1117 cout.s[cout.l - 1] = xcp[i - 1];
1122 out->l = lastenc;
1123 #ifdef HAVE_ICONV
1124 /* TODO Does not really work if we have assigned some ASCII or even
1125 * TODO translated strings because of errors! */
1126 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
1127 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
1128 convert = n_iconv_str(fhicd, n_ICONV_UNIDEFAULT, &cin, &cout, NULL);
1129 out = n_str_add(out, &cin);
1130 if (convert) {/* n_ERR_INVAL at EOS */
1131 n_iconv_reset(fhicd);
1132 out = n_str_add_buf(out, n_qm, 1); /* TODO unicode replacement */
1134 n_free(cin.s);
1135 } else
1136 #endif
1137 out = n_str_add(out, &cout);
1138 lastenc = lastoutl = out->l;
1139 n_free(cout.s);
1140 } else
1141 jnotmime: {
1142 bool_t onlyws;
1144 p = op;
1145 onlyws = (lastenc > 0);
1146 for (;;) {
1147 if (++op == upper)
1148 break;
1149 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
1150 break;
1151 if (onlyws && !blankchar(*op))
1152 onlyws = FAL0;
1155 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
1156 p = op;
1157 if (!onlyws || lastoutl != lastenc)
1158 lastenc = out->l;
1159 lastoutl = out->l;
1162 out->s[out->l] = '\0';
1164 if (flags & TD_ISPR) {
1165 makeprint(out, &cout);
1166 n_free(out->s);
1167 *out = cout;
1169 if (flags & TD_DELCTRL)
1170 out->l = delctrl(out->s, out->l);
1171 #ifdef HAVE_ICONV
1172 if (fhicd != (iconv_t)-1)
1173 n_iconv_close(fhicd);
1174 #endif
1175 jleave:
1176 NYD_LEAVE;
1177 return;
1180 FL char *
1181 mime_fromaddr(char const *name)
1183 char const *cp, *lastcp;
1184 char *res = NULL;
1185 size_t ressz = 1, rescur = 0;
1186 NYD_ENTER;
1188 if (name == NULL)
1189 goto jleave;
1190 if (*name == '\0') {
1191 res = savestr(name);
1192 goto jleave;
1195 if ((cp = routeaddr(name)) != NULL && cp > name) {
1196 _append_conv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
1197 lastcp = cp;
1198 } else
1199 cp = lastcp = name;
1201 for ( ; *cp; ++cp) {
1202 switch (*cp) {
1203 case '(':
1204 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
1205 lastcp = ++cp;
1206 cp = skip_comment(cp);
1207 if (--cp > lastcp)
1208 _append_conv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1209 lastcp = cp;
1210 break;
1211 case '"':
1212 while (*cp) {
1213 if (*++cp == '"')
1214 break;
1215 if (*cp == '\\' && cp[1] != '\0')
1216 ++cp;
1218 break;
1221 if (cp > lastcp)
1222 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1223 /* C99 */{
1224 char *x;
1226 x = res;
1227 res = savestrbuf(res, rescur);
1228 if(x != NULL)
1229 n_free(x);
1231 jleave:
1232 NYD_LEAVE;
1233 return res;
1236 FL ssize_t
1237 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1238 enum tdflags dflags, struct str * volatile outrest,
1239 struct str * volatile inrest)
1241 ssize_t rv;
1242 struct quoteflt *qf;
1243 NYD_ENTER;
1245 quoteflt_reset(qf = quoteflt_dummy(), f);
1246 rv = mime_write(ptr, size, f, convert, dflags, qf, outrest, inrest);
1247 quoteflt_flush(qf);
1248 NYD_LEAVE;
1249 return rv;
1252 static sigjmp_buf __mimemw_actjmp; /* TODO someday.. */
1253 static int __mimemw_sig; /* TODO someday.. */
1254 static sighandler_type __mimemw_opipe;
1255 static void
1256 __mimemw_onsig(int sig) /* TODO someday, we won't need it no more */
1258 NYD_X; /* Signal handler */
1259 __mimemw_sig = sig;
1260 siglongjmp(__mimemw_actjmp, 1);
1263 FL ssize_t
1264 mime_write(char const *ptr, size_t size, FILE *f,
1265 enum conversion convert, enum tdflags volatile dflags,
1266 struct quoteflt *qf, struct str * volatile outrest,
1267 struct str * volatile inrest)
1269 /* TODO note: after send/MIME layer rewrite we will have a string pool
1270 * TODO so that memory allocation count drops down massively; for now,
1271 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator.
1272 * TODO P.S.: furthermore all this encapsulated in filter objects instead */
1273 struct str in, out;
1274 ssize_t volatile sz;
1275 NYD_ENTER;
1277 dflags |= _TD_BUFCOPY;
1278 in.s = n_UNCONST(ptr);
1279 in.l = size;
1280 out.s = NULL;
1281 out.l = 0;
1283 if((sz = size) == 0){
1284 if(inrest != NULL && inrest->l != 0)
1285 goto jinrest;
1286 if(outrest != NULL && outrest->l != 0)
1287 goto jconvert;
1288 goto jleave;
1291 /* TODO This crap requires linewise input, then. We need a filter chain
1292 * TODO as in input->iconv->base64 where each filter can have its own
1293 * TODO buffer, with a filter->fflush() call to get rid of those! */
1294 #ifdef HAVE_ICONV
1295 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1296 (convert == CONV_TOQP || convert == CONV_8BIT ||
1297 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1298 if (n_iconv_str(iconvd, n_ICONV_NONE, &out, &in, NULL) != 0) {
1299 n_iconv_reset(iconvd);
1300 /* TODO This causes hard-failure. We would need to have an action
1301 * TODO policy FAIL|IGNORE|SETERROR(but continue) */
1302 sz = -1;
1303 goto jleave;
1305 in = out;
1306 out.s = NULL;
1307 dflags &= ~_TD_BUFCOPY;
1309 #endif
1311 jinrest:
1312 if(inrest != NULL && inrest->l > 0){
1313 if(size == 0){
1314 in = *inrest;
1315 inrest->s = NULL;
1316 inrest->l = 0;
1317 }else{
1318 out.s = n_alloc(in.l + inrest->l + 1);
1319 memcpy(out.s, inrest->s, inrest->l);
1320 if(in.l > 0)
1321 memcpy(&out.s[inrest->l], in.s, in.l);
1322 if(in.s != ptr)
1323 n_free(in.s);
1324 (in.s = out.s)[in.l += inrest->l] = '\0';
1325 inrest->l = 0;
1326 out.s = NULL;
1328 dflags &= ~_TD_BUFCOPY;
1331 jconvert:
1332 __mimemw_sig = 0;
1333 __mimemw_opipe = safe_signal(SIGPIPE, &__mimemw_onsig);
1334 if (sigsetjmp(__mimemw_actjmp, 1))
1335 goto jleave;
1337 switch (convert) {
1338 case CONV_FROMQP:
1339 if(!qp_decode_part(&out, &in, outrest, inrest)){
1340 n_err(_("Invalid Quoted-Printable encoding ignored\n"));
1341 sz = 0; /* TODO sz = -1 stops outer levels! */
1342 break;
1344 goto jqpb64_dec;
1345 case CONV_TOQP:
1346 if(qp_encode(&out, &in, QP_NONE) == NULL){
1347 sz = 0; /* TODO sz = -1 stops outer levels! */
1348 break;
1350 goto jqpb64_enc;
1351 case CONV_8BIT:
1352 sz = quoteflt_push(qf, in.s, in.l);
1353 break;
1354 case CONV_FROMB64:
1355 if(!b64_decode_part(&out, &in, outrest, inrest))
1356 goto jeb64;
1357 outrest = NULL;
1358 if(0){
1359 /* FALLTHRU */
1360 case CONV_FROMB64_T:
1361 if(!b64_decode_part(&out, &in, outrest, inrest)){
1362 jeb64:
1363 n_err(_("Invalid Base64 encoding ignored\n"));
1364 sz = 0; /* TODO sz = -1 stops outer levels! */
1365 break;
1368 jqpb64_dec:
1369 if ((sz = out.l) != 0) {
1370 ui32_t opl = qf->qf_pfix_len;
1371 sz = _fwrite_td(&out, FAL0, (dflags & ~_TD_BUFCOPY), outrest, qf);
1372 qf->qf_pfix_len = opl;
1374 break;
1375 case CONV_TOB64:
1376 /* TODO hack which is necessary unless this is a filter based approach
1377 * TODO and each filter has its own buffer (as necessary): we must not
1378 * TODO pass through a number of bytes which causes padding, otherwise we
1379 * TODO produce multiple adjacent base64 streams, and that is not treated
1380 * TODO in the same relaxed fashion like completely bogus bytes by at
1381 * TODO least mutt and OpenSSL. So we need an expensive workaround
1382 * TODO unless we have input->iconv->base64 filter chain as such!! :( */
1383 if(size != 0 && /* for Coverity, else assert() */ inrest != NULL){
1384 if(in.l > B64_ENCODE_INPUT_PER_LINE){
1385 size_t i;
1387 i = in.l % B64_ENCODE_INPUT_PER_LINE;
1388 in.l -= i;
1390 if(i != 0){
1391 assert(inrest->l == 0);
1392 inrest->s = n_realloc(inrest->s, i +1);
1393 memcpy(inrest->s, &in.s[in.l], i);
1394 inrest->s[inrest->l = i] = '\0';
1396 }else if(in.l < B64_ENCODE_INPUT_PER_LINE){
1397 inrest->s = n_realloc(inrest->s, in.l +1);
1398 memcpy(inrest->s, in.s, in.l);
1399 inrest->s[inrest->l = in.l] = '\0';
1400 in.l = 0;
1401 sz = 0;
1402 break;
1405 if(b64_encode(&out, &in, B64_LF | B64_MULTILINE) == NULL){
1406 sz = -1;
1407 break;
1409 jqpb64_enc:
1410 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1411 if (sz != (ssize_t)out.l)
1412 sz = -1;
1413 break;
1414 case CONV_FROMHDR:
1415 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1416 sz = quoteflt_push(qf, out.s, out.l);
1417 break;
1418 case CONV_TOHDR:
1419 sz = mime_write_tohdr(&in, f, NULL, a_MIME_SH_NONE);
1420 break;
1421 case CONV_TOHDR_A:{
1422 size_t col;
1424 if(dflags & _TD_BUFCOPY){
1425 n_str_dup(&out, &in);
1426 in = out;
1427 out.s = NULL;
1428 dflags &= ~_TD_BUFCOPY;
1430 col = 0;
1431 sz = mime_write_tohdr_a(&in, f, &col, a_MIME_SH_NONE);
1432 }break;
1433 default:
1434 sz = _fwrite_td(&in, TRU1, dflags, NULL, qf);
1435 break;
1438 jleave:
1439 if (out.s != NULL)
1440 n_free(out.s);
1441 if (in.s != ptr)
1442 n_free(in.s);
1443 safe_signal(SIGPIPE, __mimemw_opipe);
1444 if (__mimemw_sig != 0)
1445 n_raise(__mimemw_sig);
1446 NYD_LEAVE;
1447 return sz;
1450 /* s-it-mode */