nail.1: tweak `colour' docu a bit
[s-mailx.git] / mime.c
blob66f1ffef4bc5a82830c67333da4d1a6788d6f85a
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ MIME support functions.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
8 * Copyright (c) 2000
9 * Gunnar Ritter. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by Gunnar Ritter
22 * and his contributors.
23 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
39 #undef n_FILE
40 #define n_FILE mime
42 #ifndef HAVE_AMALGAMATION
43 # include "nail.h"
44 #endif
46 static char *_cs_iter_base, *_cs_iter;
47 #ifdef HAVE_ICONV
48 # define _CS_ITER_GET() ((_cs_iter != NULL) ? _cs_iter : charset_get_8bit())
49 #else
50 # define _CS_ITER_GET() ((_cs_iter != NULL) ? _cs_iter : charset_get_lc())
51 #endif
52 #define _CS_ITER_STEP() _cs_iter = n_strsep(&_cs_iter_base, ',', TRU1)
54 /* Is 7-bit enough? */
55 #ifdef HAVE_ICONV
56 static bool_t _has_highbit(char const *s);
57 static bool_t _name_highbit(struct name *np);
58 #endif
60 /* fwrite(3) while checking for displayability */
61 static ssize_t _fwrite_td(struct str const *input, enum tdflags flags,
62 struct str *outrest, struct quoteflt *qf);
64 /* Convert header fields to RFC 1522 format and write to the file fo */
65 static ssize_t mime_write_tohdr(struct str *in, FILE *fo);
67 /* Write len characters of the passed string to the passed file, doing charset
68 * and header conversion */
69 static ssize_t convhdra(char const *str, size_t len, FILE *fp);
71 /* Write an address to a header field */
72 static ssize_t mime_write_tohdr_a(struct str *in, FILE *f);
74 /* Append to buf, handling resizing */
75 static void _append_str(char **buf, size_t *sz, size_t *pos,
76 char const *str, size_t len);
77 static void _append_conv(char **buf, size_t *sz, size_t *pos,
78 char const *str, size_t len);
80 #ifdef HAVE_ICONV
81 static bool_t
82 _has_highbit(char const *s)
84 bool_t rv = TRU1;
85 NYD_ENTER;
87 if (s) {
89 if ((ui8_t)*s & 0x80)
90 goto jleave;
91 while (*s++ != '\0');
93 rv = FAL0;
94 jleave:
95 NYD_LEAVE;
96 return rv;
99 static bool_t
100 _name_highbit(struct name *np)
102 bool_t rv = TRU1;
103 NYD_ENTER;
105 while (np) {
106 if (_has_highbit(np->n_name) || _has_highbit(np->n_fullname))
107 goto jleave;
108 np = np->n_flink;
110 rv = FAL0;
111 jleave:
112 NYD_LEAVE;
113 return rv;
115 #endif /* HAVE_ICONV */
117 static sigjmp_buf __mimefwtd_actjmp; /* TODO someday.. */
118 static int __mimefwtd_sig; /* TODO someday.. */
119 static sighandler_type __mimefwtd_opipe;
120 static void
121 __mimefwtd_onsig(int sig) /* TODO someday, we won't need it no more */
123 NYD_X; /* Signal handler */
124 __mimefwtd_sig = sig;
125 siglongjmp(__mimefwtd_actjmp, 1);
128 static ssize_t
129 _fwrite_td(struct str const *input, enum tdflags flags, struct str *outrest,
130 struct quoteflt *qf)
132 /* TODO note: after send/MIME layer rewrite we will have a string pool
133 * TODO so that memory allocation count drops down massively; for now,
134 * TODO v14.* that is, we pay a lot & heavily depend on the allocator */
135 /* TODO well if we get a broken pipe here, and it happens to
136 * TODO happen pretty easy when sleeping in a full pipe buffer,
137 * TODO then the current codebase performs longjump away;
138 * TODO this leaves memory leaks behind ('think up to 3 per,
139 * TODO dep. upon alloca availability). For this to be fixed
140 * TODO we either need to get rid of the longjmp()s (tm) or
141 * TODO the storage must come from the outside or be tracked
142 * TODO in a carrier struct. Best both. But storage reuse
143 * TODO would be a bigbig win besides */
144 /* *input* _may_ point to non-modifyable buffer; but even then it only
145 * needs to be dup'ed away if we have to transform the content */
146 struct str in, out;
147 ssize_t rv;
148 NYD_ENTER;
149 n_UNUSED(outrest);
151 in = *input;
152 out.s = NULL;
153 out.l = 0;
155 #ifdef HAVE_ICONV
156 if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
157 char *buf = NULL;
159 if (outrest != NULL && outrest->l > 0) {
160 in.l = outrest->l + input->l;
161 in.s = buf = smalloc(in.l +1);
162 memcpy(in.s, outrest->s, outrest->l);
163 memcpy(&in.s[outrest->l], input->s, input->l);
164 outrest->l = 0;
167 if (n_iconv_str(iconvd, n_ICONV_UNIDEFAULT, &out, &in, &in) != 0 &&
168 outrest != NULL && in.l > 0) {
169 n_iconv_reset(iconvd);
170 /* Incomplete multibyte at EOF is special */
171 if (flags & _TD_EOF) {
172 out.s = srealloc(out.s, out.l + sizeof(n_unirepl));
173 if(options & OPT_UNICODE){
174 memcpy(&out.s[out.l], n_unirepl, sizeof(n_unirepl) -1);
175 out.l += sizeof(n_unirepl) -1;
176 }else
177 out.s[out.l++] = '?';
178 } else
179 n_str_add(outrest, &in);
181 in = out;
182 out.l = 0;
183 out.s = NULL;
184 flags &= ~_TD_BUFCOPY;
186 if (buf != NULL)
187 free(buf);
189 #endif
191 if (flags & TD_ISPR)
192 makeprint(&in, &out);
193 else if (flags & _TD_BUFCOPY)
194 n_str_dup(&out, &in);
195 else
196 out = in;
197 if (flags & TD_DELCTRL)
198 out.l = delctrl(out.s, out.l);
200 __mimefwtd_sig = 0;
201 __mimefwtd_opipe = safe_signal(SIGPIPE, &__mimefwtd_onsig);
202 if (sigsetjmp(__mimefwtd_actjmp, 1)) {
203 rv = 0;
204 goto j__sig;
207 rv = quoteflt_push(qf, out.s, out.l);
209 j__sig:
210 if (out.s != in.s)
211 free(out.s);
212 if (in.s != input->s)
213 free(in.s);
214 safe_signal(SIGPIPE, __mimefwtd_opipe);
215 if (__mimefwtd_sig != 0)
216 n_raise(__mimefwtd_sig);
217 NYD_LEAVE;
218 return rv;
221 static ssize_t
222 mime_write_tohdr(struct str *in, FILE *fo)
224 /* TODO mime_write_tohdr(): we don't know the name of our header->maxcol..
225 * TODO MIME/send layer rewrite: more available state!!
226 * TODO Because of this we cannot make a difference in between structured
227 * TODO and unstructured headers (RFC 2047, 5. (2))
228 * TODO NOT MULTIBYTE SAFE IF AN ENCODED WORD HAS TO BE SPLITTED!
229 * TODO To be better we had to mbtowc_l() (non-std! and no locale!!) and
230 * TODO work char-wise! -> S-CText..
231 * TODO The real problem for STD compatibility is however that "in" is
232 * TODO already iconv(3) encoded to the target character set! We could
233 * TODO also solve it (very expensively!) if we would narrow down to an
234 * TODO encoded word and then iconv(3)+MIME encode in one go, in which
235 * TODO case multibyte errors could be catched! */
236 enum {
237 /* Maximum line length *//* XXX we are too inflexible and could use
238 * XXX MIME_LINELEN unless an RFC 2047 encoding was actually used */
239 _MAXCOL = MIME_LINELEN_RFC2047
241 enum {
242 _FIRST = 1<<0, /* Nothing written yet, start of string */
243 _NO_QP = 1<<1, /* No quoted-printable allowed */
244 _NO_B64 = 1<<2, /* Ditto, base64 */
245 _ENC_LAST = 1<<3, /* Last round generated encoded word */
246 _SHOULD_BEE = 1<<4, /* Avoid lines longer than SHOULD via encoding */
247 _RND_SHIFT = 5,
248 _RND_MASK = (1<<_RND_SHIFT) - 1,
249 _SPACE = 1<<(_RND_SHIFT+1), /* Leading whitespace */
250 _8BIT = 1<<(_RND_SHIFT+2), /* High bit set */
251 _ENCODE = 1<<(_RND_SHIFT+3), /* Need encoding */
252 _ENC_B64 = 1<<(_RND_SHIFT+4), /* - let it be base64 */
253 _OVERLONG = 1<<(_RND_SHIFT+5) /* Temporarily rised limit */
254 } flags = _FIRST;
256 struct str cout, cin;
257 char const *cset7, *cset8, *wbot, *upper, *wend, *wcur;
258 ui32_t cset7_len, cset8_len;
259 size_t col, i, j;
260 ssize_t sz;
261 NYD_ENTER;
263 cout.s = NULL, cout.l = 0;
264 cset7 = charset_get_7bit();
265 cset7_len = (ui32_t)strlen(cset7);
266 cset8 = _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */
267 cset8_len = (ui32_t)strlen(cset8);
269 /* RFC 1468, "MIME Considerations":
270 * ISO-2022-JP may also be used in MIME Part 2 headers. The "B"
271 * encoding should be used with ISO-2022-JP text. */
272 /* TODO of course, our current implementation won't deal properly with
273 * TODO any stateful encoding at all... (the standard says each encoded
274 * TODO word must include all necessary reset sequences..., i.e., each
275 * TODO encoded word must be a self-contained iconv(3) life cycle) */
276 if (!asccasecmp(cset8, "iso-2022-jp") || mime_enc_target() == MIMEE_B64)
277 flags |= _NO_QP;
279 wbot = in->s;
280 upper = wbot + in->l;
281 col = sizeof("Mail-Followup-To: ") -1; /* dreadful thing */
283 for (sz = 0; wbot < upper; flags &= ~_FIRST, wbot = wend) {
284 flags &= _RND_MASK;
285 wcur = wbot;
286 while (wcur < upper && whitechar(*wcur)) {
287 flags |= _SPACE;
288 ++wcur;
291 /* Any occurrence of whitespace resets prevention of lines >SHOULD via
292 * enforced encoding (xxx SHOULD, but.. encoding is expensive!!) */
293 if (flags & _SPACE)
294 flags &= ~_SHOULD_BEE;
296 /* Data ends with WS - dump it and done.
297 * Also, if we have seen multiple successive whitespace characters, then
298 * if there was no encoded word last, i.e., if we can simply take them
299 * over to the output as-is, keep one WS for possible later separation
300 * purposes and simply print the others as-is, directly! */
301 if (wcur == upper) {
302 wend = wcur;
303 goto jnoenc_putws;
305 if ((flags & (_ENC_LAST | _SPACE)) == _SPACE && wcur - wbot > 1) {
306 wend = wcur - 1;
307 goto jnoenc_putws;
310 /* Skip over a word to next non-whitespace, keep track along the way
311 * whether our 7-bit charset suffices to represent the data */
312 for (wend = wcur; wend < upper; ++wend) {
313 if (whitechar(*wend))
314 break;
315 if ((uc_i)*wend & 0x80)
316 flags |= _8BIT;
319 /* Decide whether the range has to become encoded or not */
320 i = PTR2SIZE(wend - wcur);
321 j = mime_enc_mustquote(wcur, i, MIMEEF_ISHEAD);
322 /* If it just cannot fit on a SHOULD line length, force encode */
323 if (i >= _MAXCOL) {
324 flags |= _SHOULD_BEE; /* (Sigh: SHOULD only, not MUST..) */
325 goto j_beejump;
327 if ((flags & _SHOULD_BEE) || j > 0) {
328 j_beejump:
329 flags |= _ENCODE;
330 /* Use base64 if requested or more than 50% -37.5-% of the bytes of
331 * the string need to be encoded */
332 if ((flags & _NO_QP) || j >= i >> 1)/*(i >> 2) + (i >> 3))*/
333 flags |= _ENC_B64;
335 DBG( if (flags & _8BIT) assert(flags & _ENCODE); )
337 if (!(flags & _ENCODE)) {
338 /* Encoded word produced, but no linear whitespace for necessary RFC
339 * 2047 separation? Generate artificial data (bad standard!) */
340 if ((flags & (_ENC_LAST | _SPACE)) == _ENC_LAST) {
341 if (col >= _MAXCOL) {
342 putc('\n', fo);
343 ++sz;
344 col = 0;
346 putc(' ', fo);
347 ++sz;
348 ++col;
351 jnoenc_putws:
352 flags &= ~_ENC_LAST;
354 /* todo No effort here: (1) v15.0 has to bring complete rewrite,
355 * todo (2) the standard is braindead and (3) usually this is one
356 * todo word only, and why be smarter than the standard? */
357 jnoenc_retry:
358 i = PTR2SIZE(wend - wbot);
359 if (i + col <= (flags & _OVERLONG ? MIME_LINELEN_MAX : _MAXCOL)) {
360 i = fwrite(wbot, sizeof *wbot, i, fo);
361 sz += i;
362 col += i;
363 continue;
366 /* Doesn't fit, try to break the line first; */
367 if (col > 1) {
368 putc('\n', fo);
369 if (whitechar(*wbot)) {
370 putc((uc_i)*wbot, fo);
371 ++wbot;
372 } else
373 putc(' ', fo); /* Bad standard: artificial data! */
374 sz += 2;
375 col = 1;
376 flags |= _OVERLONG;
377 goto jnoenc_retry;
380 /* It is so long that it needs to be broken, effectively causing
381 * artificial spaces to be inserted (bad standard), yuck */
382 /* todo This is not multibyte safe, as above; and completely stupid
383 * todo P.S.: our _SHOULD_BEE prevents these cases in the meanwhile */
384 /* FIXME OPT_UNICODE and parse using UTF-8 sync possibility! */
385 wcur = wbot + MIME_LINELEN_MAX - 8;
386 while (wend > wcur)
387 wend -= 4;
388 goto jnoenc_retry;
389 } else {
390 /* Encoding to encoded word(s); deal with leading whitespace, place
391 * a separator first as necessary: encoded words must always be
392 * separated from text and other encoded words with linear WS.
393 * And if an encoded word was last, intermediate whitespace must
394 * also be encoded, otherwise it would get stripped away! */
395 wcur = n_UNCONST(n_empty);
396 if ((flags & (_ENC_LAST | _SPACE)) != _SPACE) {
397 /* Reinclude whitespace */
398 flags &= ~_SPACE;
399 /* We don't need to place a separator at the very beginning */
400 if (!(flags & _FIRST))
401 wcur = n_UNCONST(" ");
402 } else
403 wcur = wbot++;
405 flags |= _ENC_LAST;
406 pstate |= PS_HEADER_NEEDED_MIME;
408 /* RFC 2047:
409 * An 'encoded-word' may not be more than 75 characters long,
410 * including 'charset', 'encoding', 'encoded-text', and
411 * delimiters. If it is desirable to encode more text than will
412 * fit in an 'encoded-word' of 75 characters, multiple
413 * 'encoded-word's (separated by CRLF SPACE) may be used.
415 * While there is no limit to the length of a multiple-line
416 * header field, each line of a header field that contains one
417 * or more 'encoded-word's is limited to 76 characters */
418 jenc_retry:
419 cin.s = n_UNCONST(wbot);
420 cin.l = PTR2SIZE(wend - wbot);
422 /* C99 */{
423 struct str *xout;
425 if(flags & _ENC_B64)
426 xout = b64_encode(&cout, &cin, B64_ISHEAD | B64_ISENCWORD);
427 else
428 xout = qp_encode(&cout, &cin, QP_ISHEAD | QP_ISENCWORD);
429 if(xout == NULL){
430 sz = -1;
431 break;
433 j = xout->l;
435 /* (Avoid trigraphs in the RFC 2047 placeholder..) */
436 i = j + (flags & _8BIT ? cset8_len : cset7_len) + sizeof("=!!B!!=") -1;
437 if (*wcur != '\0')
438 ++i;
440 jenc_retry_same:
441 /* Unfortunately RFC 2047 explicitly disallows encoded words to be
442 * longer (just like RFC 5322's "a line SHOULD fit in 78 but MAY be
443 * 998 characters long"), so we cannot use the _OVERLONG mechanism,
444 * even though all tested mailers seem to support it */
445 if (i + col <= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ _MAXCOL)) {
446 fprintf(fo, "%.1s=?%s?%c?%.*s?=",
447 wcur, (flags & _8BIT ? cset8 : cset7),
448 (flags & _ENC_B64 ? 'B' : 'Q'),
449 (int)cout.l, cout.s);
450 sz += i;
451 col += i;
452 continue;
455 /* Doesn't fit, try to break the line first */
456 /* TODO I've commented out the _FIRST test since we (1) cannot do
457 * TODO _OVERLONG since (MUAs support but) the standard disallows,
458 * TODO and because of our iconv problem i prefer an empty first line
459 * TODO in favour of a possibly messed up multibytes character. :-( */
460 if (col > 1 /* TODO && !(flags & _FIRST)*/) {
461 putc('\n', fo);
462 sz += 2;
463 col = 1;
464 if (!(flags & _SPACE)) {
465 putc(' ', fo);
466 wcur = n_UNCONST(n_empty);
467 /*flags |= _OVERLONG;*/
468 goto jenc_retry_same;
469 } else {
470 putc((uc_i)*wcur, fo);
471 if (whitechar(*(wcur = wbot)))
472 ++wbot;
473 else {
474 flags &= ~_SPACE;
475 wcur = n_UNCONST(n_empty);
477 /*flags &= ~_OVERLONG;*/
478 goto jenc_retry;
482 /* It is so long that it needs to be broken, effectively causing
483 * artificial data to be inserted (bad standard), yuck */
484 /* todo This is not multibyte safe, as above */
485 /*if (!(flags & _OVERLONG)) { Mechanism explicitly forbidden by 2047
486 flags |= _OVERLONG;
487 goto jenc_retry;
490 /* FIXME OPT_UNICODE and parse using UTF-8 sync possibility! */
491 i = PTR2SIZE(wend - wbot) + !!(flags & _SPACE);
492 j = 3 + !(flags & _ENC_B64);
493 for (;;) {
494 wend -= j;
495 i -= j;
496 /* (Note the problem most likely is the transfer-encoding blow,
497 * which is why we test this *after* the decrements.. */
498 if (i <= _MAXCOL)
499 break;
501 goto jenc_retry;
505 if (cout.s != NULL)
506 free(cout.s);
507 NYD_LEAVE;
508 return sz;
511 static ssize_t
512 convhdra(char const *str, size_t len, FILE *fp)
514 #ifdef HAVE_ICONV
515 struct str ciconv;
516 #endif
517 struct str cin;
518 ssize_t ret = 0;
519 NYD_ENTER;
521 cin.s = n_UNCONST(str);
522 cin.l = len;
523 #ifdef HAVE_ICONV
524 ciconv.s = NULL;
525 if (iconvd != (iconv_t)-1) {
526 ciconv.l = 0;
527 if(n_iconv_str(iconvd, n_ICONV_IGN_NOREVERSE, &ciconv, &cin, NULL) != 0){
528 n_iconv_reset(iconvd);
529 goto jleave;
531 cin = ciconv;
533 #endif
534 ret = mime_write_tohdr(&cin, fp);
535 #ifdef HAVE_ICONV
536 jleave:
537 if (ciconv.s != NULL)
538 free(ciconv.s);
539 #endif
540 NYD_LEAVE;
541 return ret;
544 static ssize_t
545 mime_write_tohdr_a(struct str *in, FILE *f) /* TODO error handling */
547 char const *cp, *lastcp;
548 ssize_t sz, x;
549 NYD_ENTER;
551 in->s[in->l] = '\0';
552 lastcp = in->s;
553 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
554 if ((sz = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0)
555 goto jleave;
556 lastcp = cp;
557 } else {
558 cp = in->s;
559 sz = 0;
562 for ( ; *cp != '\0'; ++cp) {
563 switch (*cp) {
564 case '(':
565 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp + 1), f);
566 lastcp = ++cp;
567 cp = skip_comment(cp);
568 if (--cp > lastcp) {
569 if ((x = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0) {
570 sz = x;
571 goto jleave;
573 sz += x;
575 lastcp = cp;
576 break;
577 case '"':
578 while (*cp) {
579 if (*++cp == '"')
580 break;
581 if (*cp == '\\' && cp[1] != '\0')
582 ++cp;
584 break;
587 if (cp > lastcp)
588 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp), f);
589 jleave:
590 NYD_LEAVE;
591 return sz;
594 static void
595 _append_str(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
597 NYD_ENTER;
598 *buf = srealloc(*buf, *sz += len);
599 memcpy(&(*buf)[*pos], str, len);
600 *pos += len;
601 NYD_LEAVE;
604 static void
605 _append_conv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
607 struct str in, out;
608 NYD_ENTER;
610 in.s = n_UNCONST(str);
611 in.l = len;
612 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
613 _append_str(buf, sz, pos, out.s, out.l);
614 free(out.s);
615 NYD_LEAVE;
618 FL char const *
619 charset_get_7bit(void)
621 char const *t;
622 NYD_ENTER;
624 if ((t = ok_vlook(charset_7bit)) == NULL)
625 t = CHARSET_7BIT;
626 else{
627 char *cp;
629 cp = savestr(t);
630 for(t = cp; *cp != '\0'; ++cp)
631 *cp = lowerconv(*cp);
633 NYD_LEAVE;
634 return t;
637 #ifdef HAVE_ICONV
638 FL char const *
639 charset_get_8bit(void)
641 char const *t;
642 NYD_ENTER;
644 if ((t = ok_vlook(CHARSET_8BIT_OKEY)) == NULL)
645 t = CHARSET_8BIT;
646 else{
647 char *cp;
649 cp = savestr(t);
650 for(t = cp; *cp != '\0'; ++cp)
651 *cp = lowerconv(*cp);
653 NYD_LEAVE;
654 return t;
656 #endif
658 FL char const *
659 charset_get_lc(void)
661 char const *t;
662 NYD_ENTER;
664 if ((t = ok_vlook(ttycharset)) == NULL)
665 t = CHARSET_8BIT;
666 else{
667 char *cp;
669 cp = savestr(t);
670 for(t = cp; *cp != '\0'; ++cp)
671 *cp = lowerconv(*cp);
673 NYD_LEAVE;
674 return t;
677 FL bool_t
678 charset_iter_reset(char const *a_charset_to_try_first)
680 char const *sarr[3];
681 size_t sarrl[3], len;
682 char *cp;
683 NYD_ENTER;
684 n_UNUSED(a_charset_to_try_first);
686 #ifdef HAVE_ICONV
687 sarr[0] = a_charset_to_try_first;
688 if ((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
689 ok_blook(sendcharsets_else_ttycharset))
690 sarr[1] = charset_get_lc();
691 sarr[2] = charset_get_8bit();
692 #else
693 sarr[2] = charset_get_lc();
694 #endif
696 sarrl[2] = len = strlen(sarr[2]);
697 #ifdef HAVE_ICONV
698 if ((cp = n_UNCONST(sarr[1])) != NULL)
699 len += (sarrl[1] = strlen(cp));
700 else
701 sarrl[1] = 0;
702 if ((cp = n_UNCONST(sarr[0])) != NULL)
703 len += (sarrl[0] = strlen(cp));
704 else
705 sarrl[0] = 0;
706 #endif
708 _cs_iter_base = cp = salloc(len + 1 + 1 +1);
710 #ifdef HAVE_ICONV
711 if ((len = sarrl[0]) != 0) {
712 memcpy(cp, sarr[0], len);
713 cp[len] = ',';
714 cp += ++len;
716 if ((len = sarrl[1]) != 0) {
717 memcpy(cp, sarr[1], len);
718 cp[len] = ',';
719 cp += ++len;
721 #endif
722 len = sarrl[2];
723 memcpy(cp, sarr[2], len);
724 cp[len] = '\0';
726 _CS_ITER_STEP();
727 NYD_LEAVE;
728 return (_cs_iter != NULL);
731 FL bool_t
732 charset_iter_next(void)
734 bool_t rv;
735 NYD_ENTER;
737 _CS_ITER_STEP();
738 rv = (_cs_iter != NULL);
739 NYD_LEAVE;
740 return rv;
743 FL bool_t
744 charset_iter_is_valid(void)
746 bool_t rv;
747 NYD_ENTER;
749 rv = (_cs_iter != NULL);
750 NYD_LEAVE;
751 return rv;
754 FL char const *
755 charset_iter(void)
757 char const *rv;
758 NYD_ENTER;
760 rv = _cs_iter;
761 NYD_LEAVE;
762 return rv;
765 FL char const *
766 charset_iter_or_fallback(void)
768 char const *rv;
769 NYD_ENTER;
771 rv = _CS_ITER_GET();
772 NYD_LEAVE;
773 return rv;
776 FL void
777 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
779 NYD_ENTER;
780 outer_storage[0] = _cs_iter_base;
781 outer_storage[1] = _cs_iter;
782 NYD_LEAVE;
785 FL void
786 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
788 NYD_ENTER;
789 _cs_iter_base = outer_storage[0];
790 _cs_iter = outer_storage[1];
791 NYD_LEAVE;
794 #ifdef HAVE_ICONV
795 FL char const *
796 need_hdrconv(struct header *hp) /* TODO once only, then iter */
798 struct n_header_field *hfp;
799 char const *rv;
800 NYD_ENTER;
802 rv = NULL;
804 if((hfp = hp->h_user_headers) != NULL)
805 do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
806 goto jneeds;
807 while((hfp = hfp->hf_next) != NULL);
809 if((hfp = hp->h_custom_headers) != NULL ||
810 (hp->h_custom_headers = hfp = n_customhdr_query()) != NULL)
811 do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
812 goto jneeds;
813 while((hfp = hfp->hf_next) != NULL);
815 if (hp->h_mft != NULL) {
816 if (_name_highbit(hp->h_mft))
817 goto jneeds;
819 if (hp->h_from != NULL) {
820 if (_name_highbit(hp->h_from))
821 goto jneeds;
822 } else if (_has_highbit(myaddrs(NULL)))
823 goto jneeds;
824 if (hp->h_replyto) {
825 if (_name_highbit(hp->h_replyto))
826 goto jneeds;
827 } else if (_has_highbit(ok_vlook(replyto)))
828 goto jneeds;
829 if (hp->h_sender) {
830 if (_name_highbit(hp->h_sender))
831 goto jneeds;
832 } else if (_has_highbit(ok_vlook(sender)))
833 goto jneeds;
835 if (_name_highbit(hp->h_to))
836 goto jneeds;
837 if (_name_highbit(hp->h_cc))
838 goto jneeds;
839 if (_name_highbit(hp->h_bcc))
840 goto jneeds;
841 if (_has_highbit(hp->h_subject))
842 jneeds:
843 rv = _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
844 NYD_LEAVE;
845 return rv;
847 #endif /* HAVE_ICONV */
849 FL void
850 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
852 /* TODO mime_fromhdr(): is called with strings that contain newlines;
853 * TODO this is the usual newline problem all around the codebase;
854 * TODO i.e., if we strip it, then the display misses it ;>
855 * TODO this is why it is so messy and why S-nail v14.2 plus additional
856 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
857 * TODO why our display reflects what is contained in the message: the 1:1
858 * TODO relationship of message content and display!
859 * TODO instead a header line should be decoded to what it is (a single
860 * TODO line that is) and it should be objective to the backend whether
861 * TODO it'll be folded to fit onto the display or not, e.g., for search
862 * TODO purposes etc. then the only condition we have to honour in here
863 * TODO is that whitespace in between multiple adjacent MIME encoded words
864 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
865 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
866 struct str cin, cout;
867 char *p, *op, *upper;
868 ui32_t convert, lastenc, lastoutl;
869 #ifdef HAVE_ICONV
870 char const *tcs;
871 char *cbeg;
872 iconv_t fhicd = (iconv_t)-1;
873 #endif
874 NYD_ENTER;
876 out->l = 0;
877 if (in->l == 0) {
878 *(out->s = smalloc(1)) = '\0';
879 goto jleave;
881 out->s = NULL;
883 #ifdef HAVE_ICONV
884 tcs = charset_get_lc();
885 #endif
886 p = in->s;
887 upper = p + in->l;
888 lastenc = lastoutl = 0;
890 while (p < upper) {
891 op = p;
892 if (*p == '=' && *(p + 1) == '?') {
893 p += 2;
894 #ifdef HAVE_ICONV
895 cbeg = p;
896 #endif
897 while (p < upper && *p != '?')
898 ++p; /* strip charset */
899 if (p >= upper)
900 goto jnotmime;
901 ++p;
902 #ifdef HAVE_ICONV
903 if (flags & TD_ICONV) {
904 size_t i = PTR2SIZE(p - cbeg);
905 char *ltag, *cs = ac_alloc(i);
907 memcpy(cs, cbeg, --i);
908 cs[i] = '\0';
909 /* RFC 2231 extends the RFC 2047 character set definition in
910 * encoded words by language tags - silently strip those off */
911 if ((ltag = strchr(cs, '*')) != NULL)
912 *ltag = '\0';
914 if (fhicd != (iconv_t)-1)
915 n_iconv_close(fhicd);
916 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
917 ac_free(cs);
919 #endif
920 switch (*p) {
921 case 'B': case 'b':
922 convert = CONV_FROMB64;
923 break;
924 case 'Q': case 'q':
925 convert = CONV_FROMQP;
926 break;
927 default: /* invalid, ignore */
928 goto jnotmime;
930 if (*++p != '?')
931 goto jnotmime;
932 cin.s = ++p;
933 cin.l = 1;
934 for (;;) {
935 if (PTRCMP(p + 1, >=, upper))
936 goto jnotmime;
937 if (*p++ == '?' && *p == '=')
938 break;
939 ++cin.l;
941 ++p;
942 --cin.l;
944 cout.s = NULL;
945 cout.l = 0;
946 if (convert == CONV_FROMB64) {
947 if(!b64_decode_header(&cout, &cin))
948 n_str_assign_cp(&cout, _("[Invalid Base64 encoding]"));
949 }else if(!qp_decode_header(&cout, &cin))
950 n_str_assign_cp(&cout, _("[Invalid Quoted-Printable encoding]"));
952 out->l = lastenc;
953 #ifdef HAVE_ICONV
954 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
955 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
956 convert = n_iconv_str(fhicd, n_ICONV_UNIDEFAULT, &cin, &cout, NULL);
957 out = n_str_add(out, &cin);
958 if (convert) {/* EINVAL at EOS */
959 n_iconv_reset(fhicd);
960 out = n_str_add_buf(out, "?", 1); /* TODO unicode replacement */
962 free(cin.s);
963 } else
964 #endif
965 out = n_str_add(out, &cout);
966 lastenc = lastoutl = out->l;
967 free(cout.s);
968 } else
969 jnotmime: {
970 bool_t onlyws;
972 p = op;
973 onlyws = (lastenc > 0);
974 for (;;) {
975 if (++op == upper)
976 break;
977 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
978 break;
979 if (onlyws && !blankchar(*op))
980 onlyws = FAL0;
983 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
984 p = op;
985 if (!onlyws || lastoutl != lastenc)
986 lastenc = out->l;
987 lastoutl = out->l;
990 out->s[out->l] = '\0';
992 if (flags & TD_ISPR) {
993 makeprint(out, &cout);
994 free(out->s);
995 *out = cout;
997 if (flags & TD_DELCTRL)
998 out->l = delctrl(out->s, out->l);
999 #ifdef HAVE_ICONV
1000 if (fhicd != (iconv_t)-1)
1001 n_iconv_close(fhicd);
1002 #endif
1003 jleave:
1004 NYD_LEAVE;
1005 return;
1008 FL char *
1009 mime_fromaddr(char const *name)
1011 char const *cp, *lastcp;
1012 char *res = NULL;
1013 size_t ressz = 1, rescur = 0;
1014 NYD_ENTER;
1016 if (name == NULL)
1017 goto jleave;
1018 if (*name == '\0') {
1019 res = savestr(name);
1020 goto jleave;
1023 if ((cp = routeaddr(name)) != NULL && cp > name) {
1024 _append_conv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
1025 lastcp = cp;
1026 } else
1027 cp = lastcp = name;
1029 for ( ; *cp; ++cp) {
1030 switch (*cp) {
1031 case '(':
1032 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
1033 lastcp = ++cp;
1034 cp = skip_comment(cp);
1035 if (--cp > lastcp)
1036 _append_conv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1037 lastcp = cp;
1038 break;
1039 case '"':
1040 while (*cp) {
1041 if (*++cp == '"')
1042 break;
1043 if (*cp == '\\' && cp[1] != '\0')
1044 ++cp;
1046 break;
1049 if (cp > lastcp)
1050 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1051 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1052 if (rescur == 0)
1053 res = n_UNCONST(n_empty);
1054 else
1055 res[rescur] = '\0';
1056 { char *x = res;
1057 res = savestr(res);
1058 free(x);
1060 jleave:
1061 NYD_LEAVE;
1062 return res;
1065 FL ssize_t
1066 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1067 enum tdflags dflags)
1069 ssize_t rv;
1070 struct quoteflt *qf;
1071 NYD_ENTER;
1073 quoteflt_reset(qf = quoteflt_dummy(), f);
1074 rv = mime_write(ptr, size, f, convert, dflags, qf, NULL, NULL);
1075 quoteflt_flush(qf);
1076 NYD_LEAVE;
1077 return rv;
1080 static sigjmp_buf __mimemw_actjmp; /* TODO someday.. */
1081 static int __mimemw_sig; /* TODO someday.. */
1082 static sighandler_type __mimemw_opipe;
1083 static void
1084 __mimemw_onsig(int sig) /* TODO someday, we won't need it no more */
1086 NYD_X; /* Signal handler */
1087 __mimemw_sig = sig;
1088 siglongjmp(__mimemw_actjmp, 1);
1091 FL ssize_t
1092 mime_write(char const *ptr, size_t size, FILE *f,
1093 enum conversion convert, enum tdflags volatile dflags,
1094 struct quoteflt *qf, struct str * volatile outrest,
1095 struct str * volatile inrest)
1097 /* TODO note: after send/MIME layer rewrite we will have a string pool
1098 * TODO so that memory allocation count drops down massively; for now,
1099 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1100 struct str in, out;
1101 ssize_t volatile sz;
1102 NYD_ENTER;
1104 dflags |= _TD_BUFCOPY;
1106 in.s = n_UNCONST(ptr);
1107 in.l = size;
1108 if(inrest != NULL && inrest->l > 0){
1109 out.s = smalloc(inrest->l + size + 1);
1110 memcpy(out.s, inrest->s, inrest->l);
1111 if(size > 0)
1112 memcpy(&out.s[inrest->l], in.s, size);
1113 size += inrest->l;
1114 inrest->l = 0;
1115 (in.s = out.s)[in.l = size] = '\0';
1116 dflags &= ~_TD_BUFCOPY;
1119 out.s = NULL;
1120 out.l = 0;
1122 if ((sz = size) == 0) {
1123 if (outrest != NULL && outrest->l != 0)
1124 goto jconvert;
1125 goto jleave;
1128 #ifdef HAVE_ICONV
1129 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1130 (convert == CONV_TOQP || convert == CONV_8BIT ||
1131 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1132 if (n_iconv_str(iconvd, n_ICONV_IGN_NOREVERSE, &out, &in, NULL) != 0) {
1133 n_iconv_reset(iconvd);
1134 /* TODO This causes hard-failure. We would need to have an action
1135 * TODO policy FAIL|IGNORE|SETERROR(but continue). Better huh? */
1136 sz = -1;
1137 goto jleave;
1139 in = out;
1140 out.s = NULL;
1141 dflags &= ~_TD_BUFCOPY;
1143 #endif
1145 jconvert:
1146 __mimemw_sig = 0;
1147 __mimemw_opipe = safe_signal(SIGPIPE, &__mimemw_onsig);
1148 if (sigsetjmp(__mimemw_actjmp, 1))
1149 goto jleave;
1151 switch (convert) {
1152 case CONV_FROMQP:
1153 if(!qp_decode_text(&out, &in, outrest, inrest)){
1154 n_err(_("Invalid Quoted-Printable encoding ignored\n"));
1155 sz = 0; /* TODO sz = -1 stops outer levels! */
1156 break;
1158 goto jqpb64_dec;
1159 case CONV_TOQP:
1160 if(qp_encode(&out, &in, QP_NONE) == NULL){
1161 sz = 0; /* TODO sz = -1 stops outer levels! */
1162 break;
1164 goto jqpb64_enc;
1165 case CONV_8BIT:
1166 sz = quoteflt_push(qf, in.s, in.l);
1167 break;
1168 case CONV_FROMB64:
1169 if(!b64_decode_text(&out, &in, outrest, inrest))
1170 goto jeb64;
1171 outrest = NULL;
1172 if(0){
1173 /* FALLTHRU */
1174 case CONV_FROMB64_T:
1175 if(!b64_decode_text(&out, &in, outrest, inrest)){
1176 jeb64:
1177 n_err(_("Invalid Base64 encoding ignored\n"));
1178 sz = 0; /* TODO sz = -1 stops outer levels! */
1179 break;
1182 jqpb64_dec:
1183 if ((sz = out.l) != 0) {
1184 ui32_t opl = qf->qf_pfix_len;
1185 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), outrest, qf);
1186 qf->qf_pfix_len = opl;
1188 break;
1189 case CONV_TOB64:
1190 if(b64_encode(&out, &in, B64_LF | B64_MULTILINE) == NULL){
1191 sz = -1;
1192 break;
1194 jqpb64_enc:
1195 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1196 if (sz != (ssize_t)out.l)
1197 sz = -1;
1198 break;
1199 case CONV_FROMHDR:
1200 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1201 sz = quoteflt_push(qf, out.s, out.l);
1202 break;
1203 case CONV_TOHDR:
1204 sz = mime_write_tohdr(&in, f);
1205 break;
1206 case CONV_TOHDR_A:
1207 sz = mime_write_tohdr_a(&in, f);
1208 break;
1209 default:
1210 sz = _fwrite_td(&in, dflags, NULL, qf);
1211 break;
1213 jleave:
1214 if (out.s != NULL)
1215 free(out.s);
1216 if (in.s != ptr)
1217 free(in.s);
1218 safe_signal(SIGPIPE, __mimemw_opipe);
1219 if (__mimemw_sig != 0)
1220 n_raise(__mimemw_sig);
1221 NYD_LEAVE;
1222 return sz;
1225 /* s-it-mode */