THANKS: Coverity.com (overdue)
[s-mailx.git] / src / mx / mime.c
blob4df27d13f086ccde25891e77943e618e6d8a3114
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 - 2020 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 su_FILE
42 #define su_FILE mime
43 #define mx_SOURCE
45 #ifndef mx_HAVE_AMALGAMATION
46 # include "mx/nail.h"
47 #endif
49 #include <su/cs.h>
50 #include <su/mem.h>
51 #include <su/utf.h>
53 /* TODO nonsense (should be filter chain!) */
54 #include "mx/filter-quote.h"
55 #include "mx/iconv.h"
56 #include "mx/names.h"
57 #include "mx/sigs.h"
58 #include "mx/ui-str.h"
60 /* TODO fake */
61 #include "su/code-in.h"
63 /* Don't ask, but it keeps body and soul together */
64 enum a_mime_structure_hack{
65 a_MIME_SH_NONE,
66 a_MIME_SH_COMMENT,
67 a_MIME_SH_QUOTE
70 static char *_cs_iter_base, *_cs_iter;
71 #ifdef mx_HAVE_ICONV
72 # define _CS_ITER_GET() \
73 ((_cs_iter != NULL) ? _cs_iter : ok_vlook(CHARSET_8BIT_OKEY))
74 #else
75 # define _CS_ITER_GET() ((_cs_iter != NULL) ? _cs_iter : ok_vlook(ttycharset))
76 #endif
77 #define _CS_ITER_STEP() _cs_iter = su_cs_sep_c(&_cs_iter_base, ',', TRU1)
79 /* Is 7-bit enough? */
80 #ifdef mx_HAVE_ICONV
81 static boole _has_highbit(char const *s);
82 static boole _name_highbit(struct mx_name *np);
83 #endif
85 /* fwrite(3) while checking for displayability */
86 static sz _fwrite_td(struct str const *input, enum tdflags flags,
87 struct str *outrest, struct quoteflt *qf);
89 /* Convert header fields to RFC 2047 format and write to the file fo */
90 static sz mime_write_tohdr(struct str *in, FILE *fo,
91 uz *colp, enum a_mime_structure_hack msh);
93 #ifdef mx_HAVE_ICONV
94 static sz a_mime__convhdra(struct str *inp, FILE *fp, uz *colp,
95 enum a_mime_structure_hack msh);
96 #else
97 # define a_mime__convhdra(S,F,C,MSH) mime_write_tohdr(S, F, C, MSH)
98 #endif
100 /* Write an address to a header field */
101 static sz mime_write_tohdr_a(struct str *in, FILE *f,
102 uz *colp, enum a_mime_structure_hack msh);
104 /* Append to buf, handling resizing */
105 static void _append_str(char **buf, uz *size, uz *pos,
106 char const *str, uz len);
107 static void _append_conv(char **buf, uz *size, uz *pos,
108 char const *str, uz len);
110 #ifdef mx_HAVE_ICONV
111 static boole
112 _has_highbit(char const *s)
114 boole rv = TRU1;
115 NYD_IN;
117 if (s) {
119 if ((u8)*s & 0x80)
120 goto jleave;
121 while (*s++ != '\0');
123 rv = FAL0;
124 jleave:
125 NYD_OU;
126 return rv;
129 static boole
130 _name_highbit(struct mx_name *np)
132 boole rv = TRU1;
133 NYD_IN;
135 while (np) {
136 if (_has_highbit(np->n_name) || _has_highbit(np->n_fullname))
137 goto jleave;
138 np = np->n_flink;
140 rv = FAL0;
141 jleave:
142 NYD_OU;
143 return rv;
145 #endif /* mx_HAVE_ICONV */
147 static sigjmp_buf __mimefwtd_actjmp; /* TODO someday.. */
148 static int __mimefwtd_sig; /* TODO someday.. */
149 static n_sighdl_t __mimefwtd_opipe;
150 static void
151 __mimefwtd_onsig(int sig) /* TODO someday, we won't need it no more */
153 NYD; /* Signal handler */
154 __mimefwtd_sig = sig;
155 siglongjmp(__mimefwtd_actjmp, 1);
158 static sz
159 _fwrite_td(struct str const *input, enum tdflags flags,
160 struct str *outrest, struct quoteflt *qf)
162 /* TODO note: after send/MIME layer rewrite we will have a string pool
163 * TODO so that memory allocation count drops down massively; for now,
164 * TODO v14.* that is, we pay a lot & heavily depend on the allocator */
165 /* TODO well if we get a broken pipe here, and it happens to
166 * TODO happen pretty easy when sleeping in a full pipe buffer,
167 * TODO then the current codebase performs longjump away;
168 * TODO this leaves memory leaks behind ('think up to 3 per,
169 * TODO dep. upon alloca availability). For this to be fixed
170 * TODO we either need to get rid of the longjmp()s (tm) or
171 * TODO the storage must come from the outside or be tracked
172 * TODO in a carrier struct. Best both. But storage reuse
173 * TODO would be a bigbig win besides */
174 /* *input* _may_ point to non-modifyable buffer; but even then it only
175 * needs to be dup'ed away if we have to transform the content */
176 struct str in, out;
177 sz rv;
178 NYD_IN;
179 UNUSED(outrest);
181 in = *input;
182 out.s = NULL;
183 out.l = 0;
185 #ifdef mx_HAVE_ICONV
186 if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
187 int err;
188 char *buf;
190 buf = NULL;
192 if (outrest != NULL && outrest->l > 0) {
193 in.l = outrest->l + input->l;
194 in.s = buf = n_alloc(in.l +1);
195 su_mem_copy(in.s, outrest->s, outrest->l);
196 su_mem_copy(&in.s[outrest->l], input->s, input->l);
197 outrest->l = 0;
200 rv = 0;
202 /* TODO Sigh, no problem if we have a filter that has a buffer (or
203 * TODO become fed with entire lines, whatever), but for now we need
204 * TODO to ensure we pass entire lines from in here to iconv(3), because
205 * TODO the Citrus iconv(3) will fail tests with stateful encodings
206 * TODO if we do not (only seen on FreeBSD) */
207 #if 0 /* TODO actually not needed indeed, it was known iswprint() error! */
208 if(!(flags & _TD_EOF) && outrest != NULL){
209 uz i, j;
210 char const *cp;
212 if((cp = su_mem_find(in.s, '\n', j = in.l)) != NULL){
213 i = P2UZ(cp - in.s);
214 j -= i;
215 while(j > 0 && *cp == '\n') /* XXX one iteration too much */
216 ++cp, --j, ++i;
217 if(j != 0)
218 n_str_assign_buf(outrest, cp, j);
219 in.l = i;
220 }else{
221 n_str_assign(outrest, &in);
222 goto jleave;
225 #endif
227 if((err = n_iconv_str(iconvd, n_ICONV_UNIDEFAULT,
228 &out, &in, &in)) != 0){
229 if(err != su_ERR_INVAL)
230 n_iconv_reset(iconvd);
232 if(outrest != NULL && in.l > 0){
233 /* Incomplete multibyte at EOF is special xxx _INVAL? */
234 if (flags & _TD_EOF) {
235 out.s = n_realloc(out.s, out.l + sizeof(su_utf8_replacer));
236 if(n_psonce & n_PSO_UNICODE){
237 su_mem_copy(&out.s[out.l], su_utf8_replacer,
238 sizeof(su_utf8_replacer) -1);
239 out.l += sizeof(su_utf8_replacer) -1;
240 }else
241 out.s[out.l++] = '?';
242 } else
243 n_str_add(outrest, &in);
244 }else
245 rv = -1;
247 in = out;
248 out.l = 0;
249 out.s = NULL;
250 flags &= ~_TD_BUFCOPY;
252 if(buf != NULL)
253 n_free(buf);
254 if(rv < 0)
255 goto jleave;
256 }else
257 #endif /* mx_HAVE_ICONV */
258 /* Else, if we will modify the data bytes and thus introduce the potential
259 * of messing up multibyte sequences which become split over buffer
260 * boundaries TODO and unless we don't have our filter chain which will
261 * TODO make these hacks go by, buffer data until we see a NL */
262 if((flags & (TD_ISPR | TD_DELCTRL)) && outrest != NULL &&
263 #ifdef mx_HAVE_ICONV
264 iconvd == (iconv_t)-1 &&
265 #endif
266 (!(flags & _TD_EOF) || outrest->l > 0)
268 uz i;
269 char *cp;
271 for (cp = &in.s[in.l]; cp > in.s && cp[-1] != '\n'; --cp)
273 i = P2UZ(cp - in.s);
275 if (i != in.l) {
276 if (i > 0) {
277 n_str_assign_buf(outrest, cp, in.l - i);
278 cp = n_alloc(i +1);
279 su_mem_copy(cp, in.s, in.l = i);
280 (in.s = cp)[in.l = i] = '\0';
281 flags &= ~_TD_BUFCOPY;
282 } else {
283 n_str_add_buf(outrest, input->s, input->l);
284 rv = 0;
285 goto jleave;
290 if (flags & TD_ISPR)
291 makeprint(&in, &out);
292 else if (flags & _TD_BUFCOPY)
293 n_str_dup(&out, &in);
294 else
295 out = in;
296 if (flags & TD_DELCTRL)
297 out.l = delctrl(out.s, out.l);
299 __mimefwtd_sig = 0;
300 __mimefwtd_opipe = safe_signal(SIGPIPE, &__mimefwtd_onsig);
301 if (sigsetjmp(__mimefwtd_actjmp, 1)) {
302 rv = 0;
303 goto j__sig;
306 rv = quoteflt_push(qf, out.s, out.l);
308 j__sig:
309 if (out.s != in.s)
310 n_free(out.s);
311 if (in.s != input->s)
312 n_free(in.s);
313 safe_signal(SIGPIPE, __mimefwtd_opipe);
314 if (__mimefwtd_sig != 0)
315 n_raise(__mimefwtd_sig);
316 jleave:
317 NYD_OU;
318 return rv;
321 static sz
322 mime_write_tohdr(struct str *in, FILE *fo, uz *colp,
323 enum a_mime_structure_hack msh)
325 /* TODO mime_write_tohdr(): we don't know the name of our header->maxcol..
326 * TODO MIME/send layer rewrite: more available state!!
327 * TODO Because of this we cannot make a difference in between structured
328 * TODO and unstructured headers (RFC 2047, 5. (2))
329 * TODO This means, e.g., that this gets called multiple times for a
330 * TODO structured header and always starts thinking it is at column 0.
331 * TODO I.e., it may get called for only the content of a comment etc.,
332 * TODO not knowing anything of its context.
333 * TODO Instead we should have a list of header body content tokens,
334 * TODO convert them, and then dump the converted tokens, breaking lines.
335 * TODO I.e., get rid of convhdra, mime_write_tohdr_a and such...
336 * TODO Somewhen, the following should produce smooth stuff:
337 * TODO ' "Hallo\"," Dr. Backe "Bl\"ö\"d" (Gell) <ha@llöch.en>
338 * TODO "Nochm\"a\"l"<ta@tu.da>(Dümm)'
339 * TODO NOT MULTIBYTE SAFE IF AN ENCODED WORD HAS TO BE SPLIT!
340 * TODO To be better we had to mbtowc_l() (non-std! and no locale!!) and
341 * TODO work char-wise! -> S-CText..
342 * TODO The real problem for STD compatibility is however that "in" is
343 * TODO already iconv(3) encoded to the target character set! We could
344 * TODO also solve it (very expensively!) if we would narrow down to an
345 * TODO encoded word and then iconv(3)+MIME encode in one go, in which
346 * TODO case multibyte errors could be caught! */
347 enum {
348 /* Maximum line length */
349 a_MAXCOL_NENC = MIME_LINELEN,
350 a_MAXCOL = MIME_LINELEN_RFC2047
353 struct str cout, cin;
354 enum {
355 _FIRST = 1<<0, /* Nothing written yet, start of string */
356 _MSH_NOTHING = 1<<1, /* Now, really: nothing at all has been written */
357 a_ANYENC = 1<<2, /* We have RFC 2047 anything at least once */
358 _NO_QP = 1<<3, /* No quoted-printable allowed */
359 _NO_B64 = 1<<4, /* Ditto, base64 */
360 _ENC_LAST = 1<<5, /* Last round generated encoded word */
361 _SHOULD_BEE = 1<<6, /* Avoid lines longer than SHOULD via encoding */
362 _RND_SHIFT = 7,
363 _RND_MASK = (1<<_RND_SHIFT) - 1,
364 _SPACE = 1<<(_RND_SHIFT+1), /* Leading whitespace */
365 _8BIT = 1<<(_RND_SHIFT+2), /* High bit set */
366 _ENCODE = 1<<(_RND_SHIFT+3), /* Need encoding */
367 _ENC_B64 = 1<<(_RND_SHIFT+4), /* - let it be base64 */
368 _IF_ENC_NO_B64 = 1u<<(_RND_SHIFT+5), /* - NO! MUST NOT be base64 */
369 _OVERLONG = 1u<<(_RND_SHIFT+6) /* Temporarily raised limit */
370 } flags;
371 char const *cset7, *cset8, *wbot, *upper, *wend, *wcur;
372 u32 cset7_len, cset8_len;
373 uz col, i, j;
374 sz size;
376 NYD_IN;
378 cout.s = NULL, cout.l = 0;
379 cset7 = ok_vlook(charset_7bit);
380 cset7_len = (u32)su_cs_len(cset7);
381 cset8 = _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */
382 cset8_len = (u32)su_cs_len(cset8);
384 flags = _FIRST;
385 if(msh != a_MIME_SH_NONE)
386 flags |= _MSH_NOTHING;
388 /* RFC 1468, "MIME Considerations":
389 * ISO-2022-JP may also be used in MIME Part 2 headers. The "B"
390 * encoding should be used with ISO-2022-JP text. */
391 /* TODO of course, our current implementation won't deal properly with
392 * TODO any stateful encoding at all... (the standard says each encoded
393 * TODO word must include all necessary reset sequences..., i.e., each
394 * TODO encoded word must be a self-contained iconv(3) life cycle) */
395 if (!su_cs_cmp_case(cset8, "iso-2022-jp") || mime_enc_target() == MIMEE_B64)
396 flags |= _NO_QP;
398 wbot = in->s;
399 upper = wbot + in->l;
400 size = 0;
402 if(colp == NULL || (col = *colp) == 0)
403 col = sizeof("Mail-Followup-To: ") -1; /* TODO dreadful thing */
405 /* The user may specify empty quoted-strings or comments, keep them! */
406 if(wbot == upper) {
407 if(flags & _MSH_NOTHING){
408 flags &= ~_MSH_NOTHING;
409 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
410 size = 1;
411 ++col;
413 } else for (; wbot < upper; flags &= ~_FIRST, wbot = wend) {
414 flags &= _RND_MASK;
415 wcur = wbot;
416 while (wcur < upper && su_cs_is_white(*wcur)) {
417 flags |= _SPACE;
418 ++wcur;
421 /* Any occurrence of whitespace resets prevention of lines >SHOULD via
422 * enforced encoding (xxx SHOULD, but.. encoding is expensive!!) */
423 if (flags & _SPACE)
424 flags &= ~_SHOULD_BEE;
426 /* Data ends with WS - dump it and done.
427 * Also, if we have seen multiple successive whitespace characters, then
428 * if there was no encoded word last, i.e., if we can simply take them
429 * over to the output as-is, keep one WS for possible later separation
430 * purposes and simply print the others as-is, directly! */
431 if (wcur == upper) {
432 wend = wcur;
433 goto jnoenc_putws;
435 if ((flags & (_ENC_LAST | _SPACE)) == _SPACE && wcur - wbot > 1) {
436 wend = wcur - 1;
437 goto jnoenc_putws;
440 /* Skip over a word to next non-whitespace, keep track along the way
441 * whether our 7-bit charset suffices to represent the data */
442 for (wend = wcur; wend < upper; ++wend) {
443 if (su_cs_is_white(*wend))
444 break;
445 if ((uc)*wend & 0x80)
446 flags |= _8BIT;
447 /* pure RFC 5322 need to parse these plain */
448 else if(*wend == '"' || *wend == '(' || *wend == ')')
449 flags |= _IF_ENC_NO_B64;
452 /* Decide whether the range has to become encoded or not */
453 i = P2UZ(wend - wcur);
454 j = mime_enc_mustquote(wcur, i, MIMEEF_ISHEAD);
455 /* If it just cannot fit on a SHOULD line length, force encode */
456 if (i > a_MAXCOL_NENC) {
457 flags |= _SHOULD_BEE; /* (Sigh: SHOULD only, not MUST..) */
458 goto j_beejump;
460 if ((flags & _SHOULD_BEE) || j > 0) {
461 j_beejump:
462 flags |= _ENCODE;
463 /* Use base64 if requested or more than 50% -37.5-% of the bytes of
464 * the string need to be encoded */
465 if(flags & _IF_ENC_NO_B64)
466 flags &= ~_ENC_B64;
467 else if ((flags & _NO_QP) || j >= i >> 1)/*(i >> 2) + (i >> 3))*/
468 flags |= _ENC_B64;
470 su_DBG( if (flags & _8BIT) ASSERT(flags & _ENCODE); )
472 if (!(flags & _ENCODE)) {
473 /* Encoded word produced, but no linear whitespace for necessary RFC
474 * 2047 separation? Generate artificial data (bad standard!) */
475 if ((flags & (_ENC_LAST | _SPACE)) == _ENC_LAST) {
476 if (col >= a_MAXCOL) {
477 putc('\n', fo);
478 ++size;
479 col = 0;
481 if(flags & _MSH_NOTHING){
482 flags &= ~_MSH_NOTHING;
483 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
484 ++size;
485 ++col;
487 putc(' ', fo);
488 ++size;
489 ++col;
492 jnoenc_putws:
493 flags &= ~_ENC_LAST;
495 /* todo No effort here: (1) v15.0 has to bring complete rewrite,
496 * todo (2) the standard is braindead and (3) usually this is one
497 * todo word only, and why be smarter than the standard? */
498 jnoenc_retry:
499 i = P2UZ(wend - wbot);
500 if (i + col + ((flags & _MSH_NOTHING) != 0) <=
501 (flags & _OVERLONG ? MIME_LINELEN_MAX
502 : (flags & a_ANYENC ? a_MAXCOL : a_MAXCOL_NENC))) {
503 if(flags & _MSH_NOTHING){
504 flags &= ~_MSH_NOTHING;
505 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
506 ++size;
507 ++col;
509 i = fwrite(wbot, sizeof *wbot, i, fo);
510 size += i;
511 col += i;
512 continue;
515 /* Doesn't fit, try to break the line first; */
516 if (col > 1) {
517 putc('\n', fo);
518 if (su_cs_is_white(*wbot)) {
519 putc((uc)*wbot, fo);
520 ++wbot;
521 } else
522 putc(' ', fo); /* Bad standard: artificial data! */
523 size += 2;
524 col = 1;
525 if(flags & _MSH_NOTHING){
526 flags &= ~_MSH_NOTHING;
527 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
528 ++size;
529 ++col;
531 flags |= _OVERLONG;
532 goto jnoenc_retry;
535 /* It is so long that it needs to be broken, effectively causing
536 * artificial spaces to be inserted (bad standard), yuck */
537 /* todo This is not multibyte safe, as above; and completely stupid
538 * todo P.S.: our _SHOULD_BEE prevents these cases in the meanwhile */
539 /* FIXME n_PSO_UNICODE and parse using UTF-8 sync possibility! */
540 wcur = wbot + MIME_LINELEN_MAX - 8;
541 while (wend > wcur)
542 wend -= 4;
543 goto jnoenc_retry;
544 } else {
545 /* Encoding to encoded word(s); deal with leading whitespace, place
546 * a separator first as necessary: encoded words must always be
547 * separated from text and other encoded words with linear WS.
548 * And if an encoded word was last, intermediate whitespace must
549 * also be encoded, otherwise it would get stripped away! */
550 wcur = n_UNCONST(n_empty);
551 if ((flags & (_ENC_LAST | _SPACE)) != _SPACE) {
552 /* Reinclude whitespace */
553 flags &= ~_SPACE;
554 /* We don't need to place a separator at the very beginning */
555 if (!(flags & _FIRST))
556 wcur = n_UNCONST(" ");
557 } else
558 wcur = wbot++;
560 flags |= a_ANYENC | _ENC_LAST;
561 n_pstate |= n_PS_HEADER_NEEDED_MIME;
563 /* RFC 2047:
564 * An 'encoded-word' may not be more than 75 characters long,
565 * including 'charset', 'encoding', 'encoded-text', and
566 * delimiters. If it is desirable to encode more text than will
567 * fit in an 'encoded-word' of 75 characters, multiple
568 * 'encoded-word's (separated by CRLF SPACE) may be used.
570 * While there is no limit to the length of a multiple-line
571 * header field, each line of a header field that contains one
572 * or more 'encoded-word's is limited to 76 characters */
573 jenc_retry:
574 cin.s = n_UNCONST(wbot);
575 cin.l = P2UZ(wend - wbot);
577 /* C99 */{
578 struct str *xout;
580 if(flags & _ENC_B64)
581 xout = b64_encode(&cout, &cin, B64_ISHEAD | B64_ISENCWORD);
582 else
583 xout = qp_encode(&cout, &cin, QP_ISHEAD | QP_ISENCWORD);
584 if(xout == NULL){
585 size = -1;
586 break;
588 j = xout->l;
590 /* (Avoid trigraphs in the RFC 2047 placeholder..) */
591 i = j + (flags & _8BIT ? cset8_len : cset7_len) +
592 sizeof("=!!B!!=") -1;
593 if (*wcur != '\0')
594 ++i;
596 jenc_retry_same:
597 /* Unfortunately RFC 2047 explicitly disallows encoded words to be
598 * longer (just like RFC 5322's "a line SHOULD fit in 78 but MAY be
599 * 998 characters long"), so we cannot use the _OVERLONG mechanism,
600 * even though all tested mailers seem to support it */
601 if(i + col <= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ a_MAXCOL)){
602 if(flags & _MSH_NOTHING){
603 flags &= ~_MSH_NOTHING;
604 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
605 ++size;
606 ++col;
608 fprintf(fo, "%.1s=?%s?%c?%.*s?=",
609 wcur, (flags & _8BIT ? cset8 : cset7),
610 (flags & _ENC_B64 ? 'B' : 'Q'),
611 (int)cout.l, cout.s);
612 size += i;
613 col += i;
614 continue;
617 /* Doesn't fit, try to break the line first */
618 /* TODO I've commented out the _FIRST test since we (1) cannot do
619 * TODO _OVERLONG since (MUAs support but) the standard disallows,
620 * TODO and because of our iconv problem i prefer an empty first line
621 * TODO in favour of a possibly messed up multibytes character. :-( */
622 if (col > 1 /* TODO && !(flags & _FIRST)*/) {
623 putc('\n', fo);
624 size += 2;
625 col = 1;
626 if (!(flags & _SPACE)) {
627 putc(' ', fo);
628 wcur = n_UNCONST(n_empty);
629 /*flags |= _OVERLONG;*/
630 goto jenc_retry_same;
631 } else {
632 putc((uc)*wcur, fo);
633 if (su_cs_is_white(*(wcur = wbot)))
634 ++wbot;
635 else {
636 flags &= ~_SPACE;
637 wcur = n_UNCONST(n_empty);
639 /*flags &= ~_OVERLONG;*/
640 goto jenc_retry;
644 /* It is so long that it needs to be broken, effectively causing
645 * artificial data to be inserted (bad standard), yuck */
646 /* todo This is not multibyte safe, as above */
647 /*if (!(flags & _OVERLONG)) { Mechanism explicitly forbidden by 2047
648 flags |= _OVERLONG;
649 goto jenc_retry;
652 /* FIXME n_PSO_UNICODE and parse using UTF-8 sync possibility! */
653 i = P2UZ(wend - wbot) + !!(flags & _SPACE);
654 j = 3 + !(flags & _ENC_B64);
655 for (;;) {
656 wend -= j;
657 i -= j;
658 /* (Note the problem most likely is the transfer-encoding blow,
659 * which is why we test this *after* the decrements.. */
660 if (i <= a_MAXCOL)
661 break;
663 goto jenc_retry;
667 if(!(flags & _MSH_NOTHING) && msh != a_MIME_SH_NONE){
668 putc((msh == a_MIME_SH_COMMENT ? ')' : '"'), fo);
669 ++size;
670 ++col;
673 if(cout.s != NULL)
674 n_free(cout.s);
676 if(colp != NULL)
677 *colp = col;
678 NYD_OU;
679 return size;
682 #ifdef mx_HAVE_ICONV
683 static sz
684 a_mime__convhdra(struct str *inp, FILE *fp, uz *colp,
685 enum a_mime_structure_hack msh){
686 struct str ciconv;
687 sz rv;
688 NYD_IN;
690 rv = 0;
691 ciconv.s = NULL;
693 if(inp->l > 0 && iconvd != (iconv_t)-1){
694 ciconv.l = 0;
695 if(n_iconv_str(iconvd, n_ICONV_NONE, &ciconv, inp, NULL) != 0){
696 n_iconv_reset(iconvd);
697 rv = -1;
698 goto jleave;
700 *inp = ciconv;
703 rv = mime_write_tohdr(inp, fp, colp, msh);
704 jleave:
705 if(ciconv.s != NULL)
706 n_free(ciconv.s);
707 NYD_OU;
708 return rv;
710 #endif /* mx_HAVE_ICONV */
712 static sz
713 mime_write_tohdr_a(struct str *in, FILE *f, uz *colp,
714 enum a_mime_structure_hack msh)
716 struct str xin;
717 uz i;
718 char const *cp, *lastcp;
719 sz size, x;
720 NYD_IN;
722 in->s[in->l] = '\0';
724 if((cp = routeaddr(lastcp = in->s)) != NULL && cp > lastcp) {
725 xin.s = n_UNCONST(lastcp);
726 xin.l = P2UZ(cp - lastcp);
727 if ((size = a_mime__convhdra(&xin, f, colp, msh)) < 0)
728 goto jleave;
729 lastcp = cp;
730 } else {
731 cp = lastcp;
732 size = 0;
735 for( ; *cp != '\0'; ++cp){
736 switch(*cp){
737 case '(':
738 i = P2UZ(cp - lastcp);
739 if(i > 0){
740 if(fwrite(lastcp, 1, i, f) != i)
741 goto jerr;
742 size += i;
744 lastcp = ++cp;
745 cp = skip_comment(cp);
746 if(cp > lastcp)
747 --cp;
748 /* We want to keep empty comments, too! */
749 xin.s = n_UNCONST(lastcp);
750 xin.l = P2UZ(cp - lastcp);
751 if ((x = a_mime__convhdra(&xin, f, colp, a_MIME_SH_COMMENT)) < 0)
752 goto jerr;
753 size += x;
754 lastcp = &cp[1];
755 break;
756 case '"':
757 i = P2UZ(cp - lastcp);
758 if(i > 0){
759 if(fwrite(lastcp, 1, i, f) != i)
760 goto jerr;
761 size += i;
763 for(lastcp = ++cp; *cp != '\0'; ++cp){
764 if(*cp == '"')
765 break;
766 if(*cp == '\\' && cp[1] != '\0')
767 ++cp;
769 /* We want to keep empty quoted-strings, too! */
770 xin.s = n_UNCONST(lastcp);
771 xin.l = P2UZ(cp - lastcp);
772 if((x = a_mime__convhdra(&xin, f, colp, a_MIME_SH_QUOTE)) < 0)
773 goto jerr;
774 size += x;
775 ++size;
776 lastcp = &cp[1];
777 break;
781 i = P2UZ(cp - lastcp);
782 if(i > 0){
783 if(fwrite(lastcp, 1, i, f) != i)
784 goto jerr;
785 size += i;
787 jleave:
788 NYD_OU;
789 return size;
790 jerr:
791 size = -1;
792 goto jleave;
795 static void
796 _append_str(char **buf, uz *size, uz *pos, char const *str, uz len)
798 NYD_IN;
799 *buf = n_realloc(*buf, *size += len);
800 su_mem_copy(&(*buf)[*pos], str, len);
801 *pos += len;
802 NYD_OU;
805 static void
806 _append_conv(char **buf, uz *size, uz *pos, char const *str, uz len)
808 struct str in, out;
809 NYD_IN;
811 in.s = n_UNCONST(str);
812 in.l = len;
813 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
814 _append_str(buf, size, pos, out.s, out.l);
815 n_free(out.s);
816 NYD_OU;
819 FL boole
820 charset_iter_reset(char const *a_charset_to_try_first) /* TODO elim. dups! */
822 char const *sarr[3];
823 uz sarrl[3], len;
824 char *cp;
825 NYD_IN;
826 UNUSED(a_charset_to_try_first);
828 #ifdef mx_HAVE_ICONV
829 sarr[2] = ok_vlook(CHARSET_8BIT_OKEY);
831 if(a_charset_to_try_first != NULL &&
832 su_cs_cmp(a_charset_to_try_first, sarr[2]))
833 sarr[0] = a_charset_to_try_first;
834 else
835 sarr[0] = NULL;
837 if((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
838 ok_blook(sendcharsets_else_ttycharset)){
839 cp = n_UNCONST(ok_vlook(ttycharset));
840 if(su_cs_cmp(cp, sarr[2]) && (sarr[0] == NULL || su_cs_cmp(cp, sarr[0])))
841 sarr[1] = cp;
843 #else
844 sarr[2] = ok_vlook(ttycharset);
845 #endif
847 sarrl[2] = len = su_cs_len(sarr[2]);
848 #ifdef mx_HAVE_ICONV
849 if ((cp = n_UNCONST(sarr[1])) != NULL)
850 len += (sarrl[1] = su_cs_len(cp));
851 else
852 sarrl[1] = 0;
853 if ((cp = n_UNCONST(sarr[0])) != NULL)
854 len += (sarrl[0] = su_cs_len(cp));
855 else
856 sarrl[0] = 0;
857 #endif
859 _cs_iter_base = cp = n_autorec_alloc(len + 1 + 1 +1);
861 #ifdef mx_HAVE_ICONV
862 if ((len = sarrl[0]) != 0) {
863 su_mem_copy(cp, sarr[0], len);
864 cp[len] = ',';
865 cp += ++len;
867 if ((len = sarrl[1]) != 0) {
868 su_mem_copy(cp, sarr[1], len);
869 cp[len] = ',';
870 cp += ++len;
872 #endif
873 len = sarrl[2];
874 su_mem_copy(cp, sarr[2], len);
875 cp[len] = '\0';
877 _CS_ITER_STEP();
878 NYD_OU;
879 return (_cs_iter != NULL);
882 FL boole
883 charset_iter_next(void)
885 boole rv;
886 NYD_IN;
888 _CS_ITER_STEP();
889 rv = (_cs_iter != NULL);
890 NYD_OU;
891 return rv;
894 FL boole
895 charset_iter_is_valid(void)
897 boole rv;
898 NYD_IN;
900 rv = (_cs_iter != NULL);
901 NYD_OU;
902 return rv;
905 FL char const *
906 charset_iter(void)
908 char const *rv;
909 NYD_IN;
911 rv = _cs_iter;
912 NYD_OU;
913 return rv;
916 FL char const *
917 charset_iter_or_fallback(void)
919 char const *rv;
920 NYD_IN;
922 rv = _CS_ITER_GET();
923 NYD_OU;
924 return rv;
927 FL void
928 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
930 NYD_IN;
931 outer_storage[0] = _cs_iter_base;
932 outer_storage[1] = _cs_iter;
933 NYD_OU;
936 FL void
937 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
939 NYD_IN;
940 _cs_iter_base = outer_storage[0];
941 _cs_iter = outer_storage[1];
942 NYD_OU;
945 #ifdef mx_HAVE_ICONV
946 FL char const *
947 need_hdrconv(struct header *hp) /* TODO once only, then iter */
949 struct n_header_field *hfp;
950 char const *rv;
951 NYD_IN;
953 rv = NULL;
955 /* C99 */{
956 struct n_header_field *chlp[3]; /* TODO JOINED AFTER COMPOSE! */
957 u32 i;
959 chlp[0] = n_poption_arg_C;
960 chlp[1] = n_customhdr_list;
961 chlp[2] = hp->h_user_headers;
963 for(i = 0; i < NELEM(chlp); ++i)
964 if((hfp = chlp[i]) != NULL)
965 do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
966 goto jneeds;
967 while((hfp = hfp->hf_next) != NULL);
970 if (hp->h_mft != NULL) {
971 if (_name_highbit(hp->h_mft))
972 goto jneeds;
974 if (hp->h_from != NULL) {
975 if (_name_highbit(hp->h_from))
976 goto jneeds;
977 } else if (_has_highbit(myaddrs(NULL)))
978 goto jneeds;
979 if (hp->h_reply_to) {
980 if (_name_highbit(hp->h_reply_to))
981 goto jneeds;
982 } else {
983 char const *v15compat;
985 if((v15compat = ok_vlook(replyto)) != NULL)
986 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
987 if(_has_highbit(v15compat))
988 goto jneeds;
989 if(_has_highbit(ok_vlook(reply_to)))
990 goto jneeds;
992 if (hp->h_sender) {
993 if (_name_highbit(hp->h_sender))
994 goto jneeds;
995 } else if (_has_highbit(ok_vlook(sender)))
996 goto jneeds;
998 if (_name_highbit(hp->h_to))
999 goto jneeds;
1000 if (_name_highbit(hp->h_cc))
1001 goto jneeds;
1002 if (_name_highbit(hp->h_bcc))
1003 goto jneeds;
1004 if (_has_highbit(hp->h_subject))
1005 jneeds:
1006 rv = _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
1007 NYD_OU;
1008 return rv;
1010 #endif /* mx_HAVE_ICONV */
1012 FL void
1013 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
1015 /* TODO mime_fromhdr(): is called with strings that contain newlines;
1016 * TODO this is the usual newline problem all around the codebase;
1017 * TODO i.e., if we strip it, then the display misses it ;>
1018 * TODO this is why it is so messy and why S-nail v14.2 plus additional
1019 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
1020 * TODO why our display reflects what is contained in the message: the 1:1
1021 * TODO relationship of message content and display!
1022 * TODO instead a header line should be decoded to what it is (a single
1023 * TODO line that is) and it should be objective to the backend whether
1024 * TODO it'll be folded to fit onto the display or not, e.g., for search
1025 * TODO purposes etc. then the only condition we have to honour in here
1026 * TODO is that whitespace in between multiple adjacent MIME encoded words
1027 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
1028 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
1029 struct str cin, cout;
1030 char *p, *op, *upper;
1031 u32 convert, lastenc, lastoutl;
1032 #ifdef mx_HAVE_ICONV
1033 char const *tcs;
1034 char *cbeg;
1035 iconv_t fhicd = (iconv_t)-1;
1036 #endif
1037 NYD_IN;
1039 out->l = 0;
1040 if (in->l == 0) {
1041 *(out->s = n_alloc(1)) = '\0';
1042 goto jleave;
1044 out->s = NULL;
1046 #ifdef mx_HAVE_ICONV
1047 tcs = ok_vlook(ttycharset);
1048 #endif
1049 p = in->s;
1050 upper = p + in->l;
1051 lastenc = lastoutl = 0;
1053 while (p < upper) {
1054 op = p;
1055 if(*p == '=' && p[1] == '?'){
1056 p += 2;
1057 #ifdef mx_HAVE_ICONV
1058 cbeg = p;
1059 #endif
1060 while(p < upper && *p != '?')
1061 ++p; /* strip charset */
1062 /* ?[bq]?..?= */
1063 if(&p[4] >= upper)
1064 goto jnotmime;
1065 ++p;
1067 #ifdef mx_HAVE_ICONV
1068 if(flags & TD_ICONV){
1069 uz i;
1071 if(fhicd != R(iconv_t,-1)){
1072 n_iconv_close(fhicd);
1073 fhicd = R(iconv_t,-1);
1076 i = P2UZ(p - cbeg) - 1;
1078 if(i > 0){
1079 char *cs, *ltag;
1081 cs = n_lofi_alloc(i +1);
1083 su_mem_copy(cs, cbeg, i);
1084 cs[i] = '\0';
1086 /* RFC 2231 extends the RFC 2047 character set definition in
1087 * encoded words by language tags - silently strip those off */
1088 if((ltag = su_cs_find_c(cs, '*')) != NIL)
1089 *ltag = '\0';
1091 fhicd = su_cs_cmp_case(cs, tcs)
1092 ? n_iconv_open(tcs, cs) : R(iconv_t,-1);
1094 n_lofi_free(cs);
1097 #endif
1099 switch (*p) {
1100 case 'B': case 'b':
1101 convert = CONV_FROMB64;
1102 break;
1103 case 'Q': case 'q':
1104 convert = CONV_FROMQP;
1105 break;
1106 default: /* invalid, ignore */
1107 goto jnotmime;
1109 if (*++p != '?')
1110 goto jnotmime;
1112 cin.s = ++p;
1113 cin.l = 1;
1114 for (;;) {
1115 if (PCMP(p + 1, >=, upper))
1116 goto jnotmime;
1117 if (*p++ == '?' && *p == '=')
1118 break;
1119 ++cin.l;
1121 ++p;
1122 /* Shortcut on empty POI; _ensure_ buffer */
1123 if(--cin.l == 0){
1124 out = n_str_add_buf(out, n_qm, 1);
1125 lastenc = lastoutl = --out->l;
1126 continue;
1129 cout.s = NULL;
1130 cout.l = 0;
1131 if (convert == CONV_FROMB64) {
1132 if(!b64_decode_header(&cout, &cin))
1133 n_str_assign_cp(&cout, _("[Invalid Base64 encoding]"));
1134 }else if(!qp_decode_header(&cout, &cin))
1135 n_str_assign_cp(&cout, _("[Invalid Quoted-Printable encoding]"));
1136 /* Normalize all decoded newlines to spaces XXX only \0/\n yet */
1137 /* C99 */{
1138 char const *xcp;
1139 boole any;
1140 uz i, j;
1142 for(any = FAL0, i = cout.l; i-- != 0;)
1143 switch(cout.s[i]){
1144 case '\0':
1145 case '\n':
1146 any = TRU1;
1147 cout.s[i] = ' ';
1148 /* FALLTHRU */
1149 default:
1150 break;
1153 if(any){
1154 /* I18N: must be non-empty, last must be closing bracket/xy */
1155 xcp = _("[Content normalized: ]");
1156 i = su_cs_len(xcp);
1157 j = cout.l;
1158 n_str_add_buf(&cout, xcp, i);
1159 su_mem_move(&cout.s[i - 1], cout.s, j);
1160 su_mem_copy(&cout.s[0], xcp, i - 1);
1161 cout.s[cout.l - 1] = xcp[i - 1];
1166 out->l = lastenc;
1167 #ifdef mx_HAVE_ICONV
1168 /* TODO Does not really work if we have assigned some ASCII or even
1169 * TODO translated strings because of errors! */
1170 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
1171 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
1172 convert = n_iconv_str(fhicd, n_ICONV_UNIDEFAULT, &cin, &cout, NIL);
1173 out = n_str_add(out, &cin);
1174 if (convert) {/* su_ERR_INVAL at EOS */
1175 n_iconv_reset(fhicd);
1176 out = n_str_add_buf(out, n_qm, 1);/* TODO unicode replacement */
1178 n_free(cin.s);
1179 } else
1180 #endif
1181 out = n_str_add(out, &cout);
1182 lastenc = lastoutl = out->l;
1183 n_free(cout.s);
1184 } else
1185 jnotmime: {
1186 boole onlyws;
1188 p = op;
1189 onlyws = (lastenc > 0);
1190 for (;;) {
1191 if (++op == upper)
1192 break;
1193 if (op[0] == '=' && (PCMP(op + 1, ==, upper) || op[1] == '?'))
1194 break;
1195 if (onlyws && !su_cs_is_blank(*op))
1196 onlyws = FAL0;
1199 out = n_str_add_buf(out, p, P2UZ(op - p));
1200 p = op;
1201 if (!onlyws || lastoutl != lastenc)
1202 lastenc = out->l;
1203 lastoutl = out->l;
1207 ASSERT(out->s != NIL);
1208 out->s[out->l] = '\0';
1210 if (flags & TD_ISPR) {
1211 makeprint(out, &cout);
1212 n_free(out->s);
1213 *out = cout;
1215 if (flags & TD_DELCTRL)
1216 out->l = delctrl(out->s, out->l);
1217 #ifdef mx_HAVE_ICONV
1218 if (fhicd != (iconv_t)-1)
1219 n_iconv_close(fhicd);
1220 #endif
1221 jleave:
1222 NYD_OU;
1223 return;
1226 FL char *
1227 mime_fromaddr(char const *name)
1229 char const *cp, *lastcp;
1230 char *res = NULL;
1231 uz ressz = 1, rescur = 0;
1232 NYD_IN;
1234 if (name == NULL)
1235 goto jleave;
1236 if (*name == '\0') {
1237 res = savestr(name);
1238 goto jleave;
1241 if ((cp = routeaddr(name)) != NULL && cp > name) {
1242 _append_conv(&res, &ressz, &rescur, name, P2UZ(cp - name));
1243 lastcp = cp;
1244 } else
1245 cp = lastcp = name;
1247 for ( ; *cp; ++cp) {
1248 switch (*cp) {
1249 case '(':
1250 _append_str(&res, &ressz, &rescur, lastcp, P2UZ(cp - lastcp + 1));
1251 lastcp = ++cp;
1252 cp = skip_comment(cp);
1253 if (--cp > lastcp)
1254 _append_conv(&res, &ressz, &rescur, lastcp, P2UZ(cp - lastcp));
1255 lastcp = cp;
1256 break;
1257 case '"':
1258 while (*cp) {
1259 if (*++cp == '"')
1260 break;
1261 if (*cp == '\\' && cp[1] != '\0')
1262 ++cp;
1264 break;
1267 if (cp > lastcp)
1268 _append_str(&res, &ressz, &rescur, lastcp, P2UZ(cp - lastcp));
1269 /* C99 */{
1270 char *x;
1272 x = res;
1273 res = savestrbuf(res, rescur);
1274 if(x != NULL)
1275 n_free(x);
1277 jleave:
1278 NYD_OU;
1279 return res;
1282 FL sz
1283 xmime_write(char const *ptr, uz size, FILE *f, enum conversion convert,
1284 enum tdflags dflags, struct str * volatile outrest,
1285 struct str * volatile inrest)
1287 sz rv;
1288 struct quoteflt *qf;
1289 NYD_IN;
1291 quoteflt_reset(qf = quoteflt_dummy(), f);
1292 rv = mime_write(ptr, size, f, convert, dflags, qf, outrest, inrest);
1293 quoteflt_flush(qf);
1294 NYD_OU;
1295 return rv;
1298 static sigjmp_buf __mimemw_actjmp; /* TODO someday.. */
1299 static int __mimemw_sig; /* TODO someday.. */
1300 static n_sighdl_t __mimemw_opipe;
1301 static void
1302 __mimemw_onsig(int sig) /* TODO someday, we won't need it no more */
1304 NYD; /* Signal handler */
1305 __mimemw_sig = sig;
1306 siglongjmp(__mimemw_actjmp, 1);
1309 FL sz
1310 mime_write(char const *ptr, uz size, FILE *f,
1311 enum conversion convert, enum tdflags volatile dflags,
1312 struct quoteflt *qf, struct str * volatile outrest,
1313 struct str * volatile inrest)
1315 /* TODO note: after send/MIME layer rewrite we will have a string pool
1316 * TODO so that memory allocation count drops down massively; for now,
1317 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator.
1318 * TODO P.S.: furthermore all this encapsulated in filter objects instead */
1319 struct str in, out;
1320 sz volatile xsize;
1321 NYD_IN;
1323 dflags |= _TD_BUFCOPY;
1324 in.s = n_UNCONST(ptr);
1325 in.l = size;
1326 out.s = NULL;
1327 out.l = 0;
1329 if((xsize = size) == 0){
1330 if(inrest != NULL && inrest->l != 0)
1331 goto jinrest;
1332 if(outrest != NULL && outrest->l != 0)
1333 goto jconvert;
1334 goto jleave;
1337 /* TODO This crap requires linewise input, then. We need a filter chain
1338 * TODO as in input->iconv->base64 where each filter can have its own
1339 * TODO buffer, with a filter->fflush() call to get rid of those! */
1340 #ifdef mx_HAVE_ICONV
1341 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1342 (convert == CONV_TOQP || convert == CONV_8BIT ||
1343 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1344 if (n_iconv_str(iconvd, n_ICONV_NONE, &out, &in, NULL) != 0) {
1345 n_iconv_reset(iconvd);
1346 /* TODO This causes hard-failure. We would need to have an action
1347 * TODO policy FAIL|IGNORE|SETERROR(but continue) */
1348 xsize = -1;
1349 goto jleave;
1351 in = out;
1352 out.s = NULL;
1353 dflags &= ~_TD_BUFCOPY;
1355 #endif
1357 jinrest:
1358 if(inrest != NULL && inrest->l > 0){
1359 if(size == 0){
1360 in = *inrest;
1361 inrest->s = NULL;
1362 inrest->l = 0;
1363 }else{
1364 out.s = n_alloc(in.l + inrest->l + 1);
1365 su_mem_copy(out.s, inrest->s, inrest->l);
1366 if(in.l > 0)
1367 su_mem_copy(&out.s[inrest->l], in.s, in.l);
1368 if(in.s != ptr)
1369 n_free(in.s);
1370 (in.s = out.s)[in.l += inrest->l] = '\0';
1371 inrest->l = 0;
1372 out.s = NULL;
1374 dflags &= ~_TD_BUFCOPY;
1377 jconvert:
1378 __mimemw_sig = 0;
1379 __mimemw_opipe = safe_signal(SIGPIPE, &__mimemw_onsig);
1380 if (sigsetjmp(__mimemw_actjmp, 1))
1381 goto jleave;
1383 switch (convert) {
1384 case CONV_FROMQP:
1385 if(!qp_decode_part(&out, &in, outrest, inrest)){
1386 n_err(_("Invalid Quoted-Printable encoding ignored\n"));
1387 xsize = 0; /* TODO size = -1 stops outer levels! */
1388 break;
1390 goto jqpb64_dec;
1391 case CONV_TOQP:
1392 if(qp_encode(&out, &in, QP_NONE) == NULL){
1393 xsize = 0; /* TODO size = -1 stops outer levels! */
1394 break;
1396 goto jqpb64_enc;
1397 case CONV_8BIT:
1398 xsize = quoteflt_push(qf, in.s, in.l);
1399 break;
1400 case CONV_FROMB64:
1401 if(!b64_decode_part(&out, &in, outrest, inrest))
1402 goto jeb64;
1403 outrest = NULL;
1404 if(0){
1405 /* FALLTHRU */
1406 case CONV_FROMB64_T:
1407 if(!b64_decode_part(&out, &in, outrest, inrest)){
1408 jeb64:
1409 n_err(_("Invalid Base64 encoding ignored\n"));
1410 xsize = 0; /* TODO size = -1 stops outer levels! */
1411 break;
1414 jqpb64_dec:
1415 if ((xsize = out.l) != 0)
1416 xsize = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), outrest, qf);
1417 break;
1418 case CONV_TOB64:
1419 /* TODO hack which is necessary unless this is a filter based approach
1420 * TODO and each filter has its own buffer (as necessary): we must not
1421 * TODO pass through a number of bytes which causes padding, otherwise we
1422 * TODO produce multiple adjacent base64 streams, and that is not treated
1423 * TODO in the same relaxed fashion like completely bogus bytes by at
1424 * TODO least mutt and OpenSSL. So we need an expensive workaround
1425 * TODO unless we have input->iconv->base64 filter chain as such!! :( */
1426 if(size != 0 && /* for Coverity, else ASSERT() */ inrest != NULL){
1427 if(in.l > B64_ENCODE_INPUT_PER_LINE){
1428 uz i;
1430 i = in.l % B64_ENCODE_INPUT_PER_LINE;
1431 in.l -= i;
1433 if(i != 0){
1434 ASSERT(inrest->l == 0);
1435 inrest->s = n_realloc(inrest->s, i +1);
1436 su_mem_copy(inrest->s, &in.s[in.l], i);
1437 inrest->s[inrest->l = i] = '\0';
1439 }else if(in.l < B64_ENCODE_INPUT_PER_LINE){
1440 inrest->s = n_realloc(inrest->s, in.l +1);
1441 su_mem_copy(inrest->s, in.s, in.l);
1442 inrest->s[inrest->l = in.l] = '\0';
1443 in.l = 0;
1444 xsize = 0;
1445 break;
1448 if(b64_encode(&out, &in, B64_LF | B64_MULTILINE) == NULL){
1449 xsize = -1;
1450 break;
1452 jqpb64_enc:
1453 xsize = fwrite(out.s, sizeof *out.s, out.l, f);
1454 if (xsize != (sz)out.l)
1455 xsize = -1;
1456 break;
1457 case CONV_FROMHDR:
1458 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1459 xsize = quoteflt_push(qf, out.s, out.l);
1460 break;
1461 case CONV_TOHDR:
1462 xsize = mime_write_tohdr(&in, f, NULL, a_MIME_SH_NONE);
1463 break;
1464 case CONV_TOHDR_A:{
1465 uz col;
1467 if(dflags & _TD_BUFCOPY){
1468 n_str_dup(&out, &in);
1469 in = out;
1470 out.s = NULL;
1471 dflags &= ~_TD_BUFCOPY;
1473 col = 0;
1474 xsize = mime_write_tohdr_a(&in, f, &col, a_MIME_SH_NONE);
1475 }break;
1476 default:
1477 xsize = _fwrite_td(&in, dflags, NULL, qf);
1478 break;
1481 jleave:
1482 if (out.s != NULL)
1483 n_free(out.s);
1484 if (in.s != ptr)
1485 n_free(in.s);
1486 safe_signal(SIGPIPE, __mimemw_opipe);
1487 if (__mimemw_sig != 0)
1488 n_raise(__mimemw_sig);
1489 NYD_OU;
1490 return xsize;
1493 #include "su/code-ou.h"
1494 /* s-it-mode */