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