*inbox*: if empty, only bypass *folder* to $MAIL or builtin default
[s-mailx.git] / mime.c
blob909eb9d6704438ac365761519cb7ed7104db23e7
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 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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 *rest, 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 *rest,
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 UNUSED(rest);
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 (rest != NULL && rest->l > 0) {
160 in.l = rest->l + input->l;
161 in.s = buf = smalloc(in.l +1);
162 memcpy(in.s, rest->s, rest->l);
163 memcpy(in.s + rest->l, input->s, input->l);
164 rest->l = 0;
167 if (n_iconv_str(iconvd, &out, &in, &in, TRU1) != 0 && rest != NULL &&
168 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 + 4);
173 /* TODO 0xFFFD out.s[out.l++] = '[';*/
174 out.s[out.l++] = '?'; /* TODO 0xFFFD !!! */
175 /* TODO 0xFFFD out.s[out.l++] = ']';*/
176 } else
177 n_str_add(rest, &in);
179 in = out;
180 out.l = 0;
181 out.s = NULL;
182 flags &= ~_TD_BUFCOPY;
184 if (buf != NULL)
185 free(buf);
187 #endif
189 if (flags & TD_ISPR)
190 makeprint(&in, &out);
191 else if (flags & _TD_BUFCOPY)
192 n_str_dup(&out, &in);
193 else
194 out = in;
195 if (flags & TD_DELCTRL)
196 out.l = delctrl(out.s, out.l);
198 __mimefwtd_sig = 0;
199 __mimefwtd_opipe = safe_signal(SIGPIPE, &__mimefwtd_onsig);
200 if (sigsetjmp(__mimefwtd_actjmp, 1)) {
201 rv = 0;
202 goto j__sig;
205 rv = quoteflt_push(qf, out.s, out.l);
207 j__sig:
208 if (out.s != in.s)
209 free(out.s);
210 if (in.s != input->s)
211 free(in.s);
212 safe_signal(SIGPIPE, __mimefwtd_opipe);
213 if (__mimefwtd_sig != 0)
214 n_raise(__mimefwtd_sig);
215 NYD_LEAVE;
216 return rv;
219 static ssize_t
220 mime_write_tohdr(struct str *in, FILE *fo)
222 /* TODO mime_write_tohdr(): we don't know the name of our header->maxcol..
223 * TODO MIME/send layer rewrite: more available state!!
224 * TODO Because of this we cannot make a difference in between structured
225 * TODO and unstructured headers (RFC 2047, 5. (2))
226 * TODO NOT MULTIBYTE SAFE IF AN ENCODED WORD HAS TO BE SPLITTED!
227 * TODO To be better we had to mbtowc_l() (non-std! and no locale!!) and
228 * TODO work char-wise! -> S-CText..
229 * TODO The real problem for STD compatibility is however that "in" is
230 * TODO already iconv(3) encoded to the target character set! We could
231 * TODO also solve it (very expensively!) if we would narrow down to an
232 * TODO encoded word and then iconv(3)+MIME encode in one go, in which
233 * TODO case multibyte errors could be catched! */
234 enum {
235 /* Maximum line length *//* XXX we are too inflexible and could use
236 * XXX MIME_LINELEN unless an RFC 2047 encoding was actually used */
237 _MAXCOL = MIME_LINELEN_RFC2047
239 enum {
240 _FIRST = 1<<0, /* Nothing written yet, start of string */
241 _NO_QP = 1<<1, /* No quoted-printable allowed */
242 _NO_B64 = 1<<2, /* Ditto, base64 */
243 _ENC_LAST = 1<<3, /* Last round generated encoded word */
244 _SHOULD_BEE = 1<<4, /* Avoid lines longer than SHOULD via encoding */
245 _RND_SHIFT = 5,
246 _RND_MASK = (1<<_RND_SHIFT) - 1,
247 _SPACE = 1<<(_RND_SHIFT+1), /* Leading whitespace */
248 _8BIT = 1<<(_RND_SHIFT+2), /* High bit set */
249 _ENCODE = 1<<(_RND_SHIFT+3), /* Need encoding */
250 _ENC_B64 = 1<<(_RND_SHIFT+4), /* - let it be base64 */
251 _OVERLONG = 1<<(_RND_SHIFT+5) /* Temporarily rised limit */
252 } flags = _FIRST;
254 struct str cout, cin;
255 char const *cset7, *cset8, *wbot, *upper, *wend, *wcur;
256 ui32_t cset7_len, cset8_len;
257 size_t col, i, j;
258 ssize_t sz;
259 NYD_ENTER;
261 cout.s = NULL, cout.l = 0;
262 cset7 = charset_get_7bit();
263 cset7_len = (ui32_t)strlen(cset7);
264 cset8 = _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */
265 cset8_len = (ui32_t)strlen(cset8);
267 /* RFC 1468, "MIME Considerations":
268 * ISO-2022-JP may also be used in MIME Part 2 headers. The "B"
269 * encoding should be used with ISO-2022-JP text. */
270 /* TODO of course, our current implementation won't deal properly with
271 * TODO any stateful encoding at all... (the standard says each encoded
272 * TODO word must include all necessary reset sequences..., i.e., each
273 * TODO encoded word must be a self-contained iconv(3) life cycle) */
274 if (!asccasecmp(cset8, "iso-2022-jp"))
275 flags |= _NO_QP;
277 wbot = in->s;
278 upper = wbot + in->l;
279 col = sizeof("Mail-Followup-To: ") -1; /* dreadful thing */
281 for (sz = 0; wbot < upper; flags &= ~_FIRST, wbot = wend) {
282 flags &= _RND_MASK;
283 wcur = wbot;
284 while (wcur < upper && whitechar(*wcur)) {
285 flags |= _SPACE;
286 ++wcur;
289 /* Any occurrence of whitespace resets prevention of lines >SHOULD via
290 * enforced encoding (xxx SHOULD, but.. encoding is expensive!!) */
291 if (flags & _SPACE)
292 flags &= ~_SHOULD_BEE;
294 /* Data ends with WS - dump it and done.
295 * Also, if we have seen multiple successive whitespace characters, then
296 * if there was no encoded word last, i.e., if we can simply take them
297 * over to the output as-is, keep one WS for possible later separation
298 * purposes and simply print the others as-is, directly! */
299 if (wcur == upper) {
300 wend = wcur;
301 goto jnoenc_putws;
303 if ((flags & (_ENC_LAST | _SPACE)) == _SPACE && wcur - wbot > 1) {
304 wend = wcur - 1;
305 goto jnoenc_putws;
308 /* Skip over a word to next non-whitespace, keep track along the way
309 * wether our 7-bit charset suffices to represent the data */
310 for (wend = wcur; wend < upper; ++wend) {
311 if (whitechar(*wend))
312 break;
313 if ((uc_i)*wend & 0x80)
314 flags |= _8BIT;
317 /* Decide wether the range has to become encoded or not */
318 i = PTR2SIZE(wend - wcur);
319 j = mime_enc_mustquote(wcur, i, MIMEEF_ISHEAD);
320 /* If it just cannot fit on a SHOULD line length, force encode */
321 if (i >= _MAXCOL) {
322 flags |= _SHOULD_BEE; /* (Sigh: SHOULD only, not MUST..) */
323 goto j_beejump;
325 if ((flags & _SHOULD_BEE) || j > 0) {
326 j_beejump:
327 flags |= _ENCODE;
328 /* Use base64 if requested or more than 50% -37.5-% of the bytes of
329 * the string need to be encoded */
330 if ((flags & _NO_QP) || j >= i >> 1)/*(i >> 2) + (i >> 3))*/
331 flags |= _ENC_B64;
333 DBG( if (flags & _8BIT) assert(flags & _ENCODE); )
335 if (!(flags & _ENCODE)) {
336 /* Encoded word produced, but no linear whitespace for necessary RFC
337 * 2047 separation? Generate artificial data (bad standard!) */
338 if ((flags & (_ENC_LAST | _SPACE)) == _ENC_LAST) {
339 if (col >= _MAXCOL) {
340 putc('\n', fo);
341 ++sz;
342 col = 0;
344 putc(' ', fo);
345 ++sz;
346 ++col;
349 jnoenc_putws:
350 flags &= ~_ENC_LAST;
352 /* todo No effort here: (1) v15.0 has to bring complete rewrite,
353 * todo (2) the standard is braindead and (3) usually this is one
354 * todo word only, and why be smarter than the standard? */
355 jnoenc_retry:
356 i = PTR2SIZE(wend - wbot);
357 if (i + col <= (flags & _OVERLONG ? MIME_LINELEN_MAX : _MAXCOL)) {
358 i = fwrite(wbot, sizeof *wbot, i, fo);
359 sz += i;
360 col += i;
361 continue;
364 /* Doesn't fit, try to break the line first; */
365 if (col > 1) {
366 putc('\n', fo);
367 if (whitechar(*wbot)) {
368 putc((uc_i)*wbot, fo);
369 ++wbot;
370 } else
371 putc(' ', fo); /* Bad standard: artificial data! */
372 sz += 2;
373 col = 1;
374 flags |= _OVERLONG;
375 goto jnoenc_retry;
378 /* It is so long that it needs to be broken, effectively causing
379 * artificial spaces to be inserted (bad standard), yuck */
380 /* todo This is not multibyte safe, as above; and completely stupid
381 * todo P.S.: our _SHOULD_BEE prevents these cases in the meanwhile */
382 /* FIXME OPT_UNICODE and parse using UTF-8 sync possibility! */
383 wcur = wbot + MIME_LINELEN_MAX - 8;
384 while (wend > wcur)
385 wend -= 4;
386 goto jnoenc_retry;
387 } else {
388 /* Encoding to encoded word(s); deal with leading whitespace, place
389 * a separator first as necessary: encoded words must always be
390 * separated from text and other encoded words with linear WS.
391 * And if an encoded word was last, intermediate whitespace must
392 * also be encoded, otherwise it would get stripped away! */
393 wcur = UNCONST("");
394 if ((flags & (_ENC_LAST | _SPACE)) != _SPACE) {
395 /* Reinclude whitespace */
396 flags &= ~_SPACE;
397 /* We don't need to place a separator at the very beginning */
398 if (!(flags & _FIRST))
399 wcur = UNCONST(" ");
400 } else
401 wcur = wbot++;
403 flags |= _ENC_LAST;
404 pstate |= PS_HEADER_NEEDED_MIME;
406 /* RFC 2047:
407 * An 'encoded-word' may not be more than 75 characters long,
408 * including 'charset', 'encoding', 'encoded-text', and
409 * delimiters. If it is desirable to encode more text than will
410 * fit in an 'encoded-word' of 75 characters, multiple
411 * 'encoded-word's (separated by CRLF SPACE) may be used.
413 * While there is no limit to the length of a multiple-line
414 * header field, each line of a header field that contains one
415 * or more 'encoded-word's is limited to 76 characters */
416 jenc_retry:
417 cin.s = UNCONST(wbot);
418 cin.l = PTR2SIZE(wend - wbot);
420 if (flags & _ENC_B64)
421 j = b64_encode(&cout, &cin, B64_ISHEAD | B64_ISENCWORD)->l;
422 else
423 j = qp_encode(&cout, &cin, QP_ISHEAD | QP_ISENCWORD)->l;
424 /* (Avoid trigraphs in the RFC 2047 placeholder..) */
425 i = j + (flags & _8BIT ? cset8_len : cset7_len) + sizeof("=!!B!!=") -1;
426 if (*wcur != '\0')
427 ++i;
429 jenc_retry_same:
430 /* Unfortunately RFC 2047 explicitly disallows encoded words to be
431 * longer (just like RFC 5322's "a line SHOULD fit in 78 but MAY be
432 * 998 characters long"), so we cannot use the _OVERLONG mechanism,
433 * even though all tested mailers seem to support it */
434 if (i + col <= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ _MAXCOL)) {
435 fprintf(fo, "%.1s=?%s?%c?%.*s?=",
436 wcur, (flags & _8BIT ? cset8 : cset7),
437 (flags & _ENC_B64 ? 'B' : 'Q'),
438 (int)cout.l, cout.s);
439 sz += i;
440 col += i;
441 continue;
444 /* Doesn't fit, try to break the line first */
445 /* TODO I've commented out the _FIRST test since we (1) cannot do
446 * TODO _OVERLONG since (MUAs support but) the standard disallows,
447 * TODO and because of our iconv problem i prefer an empty first line
448 * TODO in favour of a possibly messed up multibytes character. :-( */
449 if (col > 1 /* TODO && !(flags & _FIRST)*/) {
450 putc('\n', fo);
451 sz += 2;
452 col = 1;
453 if (!(flags & _SPACE)) {
454 putc(' ', fo);
455 wcur = UNCONST("");
456 /*flags |= _OVERLONG;*/
457 goto jenc_retry_same;
458 } else {
459 putc((uc_i)*wcur, fo);
460 if (whitechar(*(wcur = wbot)))
461 ++wbot;
462 else {
463 flags &= ~_SPACE;
464 wcur = UNCONST("");
466 /*flags &= ~_OVERLONG;*/
467 goto jenc_retry;
471 /* It is so long that it needs to be broken, effectively causing
472 * artificial data to be inserted (bad standard), yuck */
473 /* todo This is not multibyte safe, as above */
474 /*if (!(flags & _OVERLONG)) { Mechanism explicitly forbidden by 2047
475 flags |= _OVERLONG;
476 goto jenc_retry;
479 /* FIXME OPT_UNICODE and parse using UTF-8 sync possibility! */
480 i = PTR2SIZE(wend - wbot) + !!(flags & _SPACE);
481 j = 3 + !(flags & _ENC_B64);
482 for (;;) {
483 wend -= j;
484 i -= j;
485 /* (Note the problem most likely is the transfer-encoding blow,
486 * which is why we test this *after* the decrements.. */
487 if (i <= _MAXCOL)
488 break;
490 goto jenc_retry;
494 if (cout.s != NULL)
495 free(cout.s);
496 NYD_LEAVE;
497 return sz;
500 static ssize_t
501 convhdra(char const *str, size_t len, FILE *fp)
503 #ifdef HAVE_ICONV
504 struct str ciconv;
505 #endif
506 struct str cin;
507 ssize_t ret = 0;
508 NYD_ENTER;
510 cin.s = UNCONST(str);
511 cin.l = len;
512 #ifdef HAVE_ICONV
513 ciconv.s = NULL;
514 if (iconvd != (iconv_t)-1) {
515 ciconv.l = 0;
516 if (n_iconv_str(iconvd, &ciconv, &cin, NULL, FAL0) != 0) {
517 n_iconv_reset(iconvd);
518 goto jleave;
520 cin = ciconv;
522 #endif
523 ret = mime_write_tohdr(&cin, fp);
524 #ifdef HAVE_ICONV
525 jleave:
526 if (ciconv.s != NULL)
527 free(ciconv.s);
528 #endif
529 NYD_LEAVE;
530 return ret;
533 static ssize_t
534 mime_write_tohdr_a(struct str *in, FILE *f) /* TODO error handling */
536 char const *cp, *lastcp;
537 ssize_t sz, x;
538 NYD_ENTER;
540 in->s[in->l] = '\0';
541 lastcp = in->s;
542 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
543 if ((sz = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0)
544 goto jleave;
545 lastcp = cp;
546 } else {
547 cp = in->s;
548 sz = 0;
551 for ( ; *cp != '\0'; ++cp) {
552 switch (*cp) {
553 case '(':
554 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp + 1), f);
555 lastcp = ++cp;
556 cp = skip_comment(cp);
557 if (--cp > lastcp) {
558 if ((x = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0) {
559 sz = x;
560 goto jleave;
562 sz += x;
564 lastcp = cp;
565 break;
566 case '"':
567 while (*cp) {
568 if (*++cp == '"')
569 break;
570 if (*cp == '\\' && cp[1] != '\0')
571 ++cp;
573 break;
576 if (cp > lastcp)
577 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp), f);
578 jleave:
579 NYD_LEAVE;
580 return sz;
583 static void
584 _append_str(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
586 NYD_ENTER;
587 *buf = srealloc(*buf, *sz += len);
588 memcpy(&(*buf)[*pos], str, len);
589 *pos += len;
590 NYD_LEAVE;
593 static void
594 _append_conv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
596 struct str in, out;
597 NYD_ENTER;
599 in.s = UNCONST(str);
600 in.l = len;
601 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
602 _append_str(buf, sz, pos, out.s, out.l);
603 free(out.s);
604 NYD_LEAVE;
607 FL char const *
608 charset_get_7bit(void)
610 char const *t;
611 NYD_ENTER;
613 if ((t = ok_vlook(charset_7bit)) == NULL)
614 t = CHARSET_7BIT;
615 NYD_LEAVE;
616 return t;
619 #ifdef HAVE_ICONV
620 FL char const *
621 charset_get_8bit(void)
623 char const *t;
624 NYD_ENTER;
626 if ((t = ok_vlook(CHARSET_8BIT_OKEY)) == NULL)
627 t = CHARSET_8BIT;
628 NYD_LEAVE;
629 return t;
631 #endif
633 FL char const *
634 charset_get_lc(void)
636 char const *t;
637 NYD_ENTER;
639 if ((t = ok_vlook(ttycharset)) == NULL)
640 t = CHARSET_8BIT;
641 NYD_LEAVE;
642 return t;
645 FL bool_t
646 charset_iter_reset(char const *a_charset_to_try_first)
648 char const *sarr[3];
649 size_t sarrl[3], len;
650 char *cp;
651 NYD_ENTER;
652 UNUSED(a_charset_to_try_first);
654 #ifdef HAVE_ICONV
655 sarr[0] = a_charset_to_try_first;
656 if ((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
657 ok_blook(sendcharsets_else_ttycharset))
658 sarr[1] = charset_get_lc();
659 sarr[2] = charset_get_8bit();
660 #else
661 sarr[2] = charset_get_lc();
662 #endif
664 sarrl[2] = len = strlen(sarr[2]);
665 #ifdef HAVE_ICONV
666 if ((cp = UNCONST(sarr[1])) != NULL)
667 len += (sarrl[1] = strlen(cp));
668 else
669 sarrl[1] = 0;
670 if ((cp = UNCONST(sarr[0])) != NULL)
671 len += (sarrl[0] = strlen(cp));
672 else
673 sarrl[0] = 0;
674 #endif
676 _cs_iter_base = cp = salloc(len + 1 + 1 +1);
678 #ifdef HAVE_ICONV
679 if ((len = sarrl[0]) != 0) {
680 memcpy(cp, sarr[0], len);
681 cp[len] = ',';
682 cp += ++len;
684 if ((len = sarrl[1]) != 0) {
685 memcpy(cp, sarr[1], len);
686 cp[len] = ',';
687 cp += ++len;
689 #endif
690 len = sarrl[2];
691 memcpy(cp, sarr[2], len);
692 cp[len] = '\0';
694 _CS_ITER_STEP();
695 NYD_LEAVE;
696 return (_cs_iter != NULL);
699 FL bool_t
700 charset_iter_next(void)
702 bool_t rv;
703 NYD_ENTER;
705 _CS_ITER_STEP();
706 rv = (_cs_iter != NULL);
707 NYD_LEAVE;
708 return rv;
711 FL bool_t
712 charset_iter_is_valid(void)
714 bool_t rv;
715 NYD_ENTER;
717 rv = (_cs_iter != NULL);
718 NYD_LEAVE;
719 return rv;
722 FL char const *
723 charset_iter(void)
725 char const *rv;
726 NYD_ENTER;
728 rv = _cs_iter;
729 NYD_LEAVE;
730 return rv;
733 FL char const *
734 charset_iter_or_fallback(void)
736 char const *rv;
737 NYD_ENTER;
739 rv = _CS_ITER_GET();
740 NYD_LEAVE;
741 return rv;
744 FL void
745 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
747 NYD_ENTER;
748 outer_storage[0] = _cs_iter_base;
749 outer_storage[1] = _cs_iter;
750 NYD_LEAVE;
753 FL void
754 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
756 NYD_ENTER;
757 _cs_iter_base = outer_storage[0];
758 _cs_iter = outer_storage[1];
759 NYD_LEAVE;
762 #ifdef HAVE_ICONV
763 FL char const *
764 need_hdrconv(struct header *hp, enum gfield w) /* TODO once only, then iter */
766 char const *ret = NULL;
767 NYD_ENTER;
769 if (w & GIDENT) {
770 if (hp->h_mft != NULL) {
771 if (_name_highbit(hp->h_mft))
772 goto jneeds;
774 if (hp->h_from != NULL) {
775 if (_name_highbit(hp->h_from))
776 goto jneeds;
777 } else if (_has_highbit(myaddrs(NULL)))
778 goto jneeds;
779 if (hp->h_organization) {
780 if (_has_highbit(hp->h_organization))
781 goto jneeds;
782 } else if (_has_highbit(ok_vlook(ORGANIZATION)))
783 goto jneeds;
784 if (hp->h_replyto) {
785 if (_name_highbit(hp->h_replyto))
786 goto jneeds;
787 } else if (_has_highbit(ok_vlook(replyto)))
788 goto jneeds;
789 if (hp->h_sender) {
790 if (_name_highbit(hp->h_sender))
791 goto jneeds;
792 } else if (_has_highbit(ok_vlook(sender)))
793 goto jneeds;
795 if ((w & GTO) && _name_highbit(hp->h_to))
796 goto jneeds;
797 if ((w & GCC) && _name_highbit(hp->h_cc))
798 goto jneeds;
799 if ((w & GBCC) && _name_highbit(hp->h_bcc))
800 goto jneeds;
801 if ((w & GSUBJECT) && _has_highbit(hp->h_subject))
802 jneeds:
803 ret = _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
804 NYD_LEAVE;
805 return ret;
807 #endif /* HAVE_ICONV */
809 FL void
810 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
812 /* TODO mime_fromhdr(): is called with strings that contain newlines;
813 * TODO this is the usual newline problem all around the codebase;
814 * TODO i.e., if we strip it, then the display misses it ;>
815 * TODO this is why it is so messy and why S-nail v14.2 plus additional
816 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
817 * TODO why our display reflects what is contained in the message: the 1:1
818 * TODO relationship of message content and display!
819 * TODO instead a header line should be decoded to what it is (a single
820 * TODO line that is) and it should be objective to the backend wether
821 * TODO it'll be folded to fit onto the display or not, e.g., for search
822 * TODO purposes etc. then the only condition we have to honour in here
823 * TODO is that whitespace in between multiple adjacent MIME encoded words
824 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
825 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
826 struct str cin, cout;
827 char *p, *op, *upper;
828 ui32_t convert, lastenc, lastoutl;
829 #ifdef HAVE_ICONV
830 char const *tcs;
831 char *cbeg;
832 iconv_t fhicd = (iconv_t)-1;
833 #endif
834 NYD_ENTER;
836 out->l = 0;
837 if (in->l == 0) {
838 *(out->s = smalloc(1)) = '\0';
839 goto jleave;
841 out->s = NULL;
843 #ifdef HAVE_ICONV
844 tcs = charset_get_lc();
845 #endif
846 p = in->s;
847 upper = p + in->l;
848 lastenc = lastoutl = 0;
850 while (p < upper) {
851 op = p;
852 if (*p == '=' && *(p + 1) == '?') {
853 p += 2;
854 #ifdef HAVE_ICONV
855 cbeg = p;
856 #endif
857 while (p < upper && *p != '?')
858 ++p; /* strip charset */
859 if (p >= upper)
860 goto jnotmime;
861 ++p;
862 #ifdef HAVE_ICONV
863 if (flags & TD_ICONV) {
864 size_t i = PTR2SIZE(p - cbeg);
865 char *ltag, *cs = ac_alloc(i);
867 memcpy(cs, cbeg, --i);
868 cs[i] = '\0';
869 /* RFC 2231 extends the RFC 2047 character set definition in
870 * encoded words by language tags - silently strip those off */
871 if ((ltag = strchr(cs, '*')) != NULL)
872 *ltag = '\0';
874 if (fhicd != (iconv_t)-1)
875 n_iconv_close(fhicd);
876 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
877 ac_free(cs);
879 #endif
880 switch (*p) {
881 case 'B': case 'b':
882 convert = CONV_FROMB64;
883 break;
884 case 'Q': case 'q':
885 convert = CONV_FROMQP;
886 break;
887 default: /* invalid, ignore */
888 goto jnotmime;
890 if (*++p != '?')
891 goto jnotmime;
892 cin.s = ++p;
893 cin.l = 1;
894 for (;;) {
895 if (PTRCMP(p + 1, >=, upper))
896 goto jnotmime;
897 if (*p++ == '?' && *p == '=')
898 break;
899 ++cin.l;
901 ++p;
902 --cin.l;
904 cout.s = NULL;
905 cout.l = 0;
906 if (convert == CONV_FROMB64) {
907 /* XXX Take care for, and strip LF from
908 * XXX [Invalid Base64 encoding ignored] */
909 if (b64_decode(&cout, &cin, NULL) == STOP &&
910 cout.s[cout.l - 1] == '\n')
911 --cout.l;
912 } else
913 qp_decode(&cout, &cin, NULL);
915 out->l = lastenc;
916 #ifdef HAVE_ICONV
917 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
918 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
919 convert = n_iconv_str(fhicd, &cin, &cout, NULL, TRU1);
920 out = n_str_add(out, &cin);
921 if (convert) {/* EINVAL at EOS */
922 n_iconv_reset(fhicd);
923 out = n_str_add_buf(out, "?", 1);
925 free(cin.s);
926 } else
927 #endif
928 out = n_str_add(out, &cout);
929 lastenc = lastoutl = out->l;
930 free(cout.s);
931 } else
932 jnotmime: {
933 bool_t onlyws;
935 p = op;
936 onlyws = (lastenc > 0);
937 for (;;) {
938 if (++op == upper)
939 break;
940 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
941 break;
942 if (onlyws && !blankchar(*op))
943 onlyws = FAL0;
946 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
947 p = op;
948 if (!onlyws || lastoutl != lastenc)
949 lastenc = out->l;
950 lastoutl = out->l;
953 out->s[out->l] = '\0';
955 if (flags & TD_ISPR) {
956 makeprint(out, &cout);
957 free(out->s);
958 *out = cout;
960 if (flags & TD_DELCTRL)
961 out->l = delctrl(out->s, out->l);
962 #ifdef HAVE_ICONV
963 if (fhicd != (iconv_t)-1)
964 n_iconv_close(fhicd);
965 #endif
966 jleave:
967 NYD_LEAVE;
968 return;
971 FL char *
972 mime_fromaddr(char const *name)
974 char const *cp, *lastcp;
975 char *res = NULL;
976 size_t ressz = 1, rescur = 0;
977 NYD_ENTER;
979 if (name == NULL)
980 goto jleave;
981 if (*name == '\0') {
982 res = savestr(name);
983 goto jleave;
986 if ((cp = routeaddr(name)) != NULL && cp > name) {
987 _append_conv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
988 lastcp = cp;
989 } else
990 cp = lastcp = name;
992 for ( ; *cp; ++cp) {
993 switch (*cp) {
994 case '(':
995 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
996 lastcp = ++cp;
997 cp = skip_comment(cp);
998 if (--cp > lastcp)
999 _append_conv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1000 lastcp = cp;
1001 break;
1002 case '"':
1003 while (*cp) {
1004 if (*++cp == '"')
1005 break;
1006 if (*cp == '\\' && cp[1] != '\0')
1007 ++cp;
1009 break;
1012 if (cp > lastcp)
1013 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1014 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1015 if (rescur == 0)
1016 res = UNCONST("");
1017 else
1018 res[rescur] = '\0';
1019 { char *x = res;
1020 res = savestr(res);
1021 free(x);
1023 jleave:
1024 NYD_LEAVE;
1025 return res;
1028 FL ssize_t
1029 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1030 enum tdflags dflags)
1032 ssize_t rv;
1033 struct quoteflt *qf;
1034 NYD_ENTER;
1036 quoteflt_reset(qf = quoteflt_dummy(), f);
1037 rv = mime_write(ptr, size, f, convert, dflags, qf, NULL);
1038 quoteflt_flush(qf);
1039 NYD_LEAVE;
1040 return rv;
1043 static sigjmp_buf __mimemw_actjmp; /* TODO someday.. */
1044 static int __mimemw_sig; /* TODO someday.. */
1045 static sighandler_type __mimemw_opipe;
1046 static void
1047 __mimemw_onsig(int sig) /* TODO someday, we won't need it no more */
1049 NYD_X; /* Signal handler */
1050 __mimemw_sig = sig;
1051 siglongjmp(__mimemw_actjmp, 1);
1054 FL ssize_t
1055 mime_write(char const *ptr, size_t size, FILE *f,
1056 enum conversion convert, enum tdflags volatile dflags,
1057 struct quoteflt *qf, struct str * volatile rest)
1059 /* TODO note: after send/MIME layer rewrite we will have a string pool
1060 * TODO so that memory allocation count drops down massively; for now,
1061 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1062 struct str in, out;
1063 ssize_t sz;
1064 int state;
1065 NYD_ENTER;
1067 in.s = UNCONST(ptr);
1068 in.l = size;
1069 out.s = NULL;
1070 out.l = 0;
1072 dflags |= _TD_BUFCOPY;
1073 if ((sz = size) == 0) {
1074 if (rest != NULL && rest->l != 0)
1075 goto jconvert;
1076 goto jleave;
1079 #ifdef HAVE_ICONV
1080 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1081 (convert == CONV_TOQP || convert == CONV_8BIT ||
1082 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1083 if (n_iconv_str(iconvd, &out, &in, NULL, FAL0) != 0) {
1084 /* XXX report conversion error? */;
1085 n_iconv_reset(iconvd);
1086 sz = -1;
1087 goto jleave;
1089 in = out;
1090 out.s = NULL;
1091 dflags &= ~_TD_BUFCOPY;
1093 #endif
1095 jconvert:
1096 __mimemw_sig = 0;
1097 __mimemw_opipe = safe_signal(SIGPIPE, &__mimemw_onsig);
1098 if (sigsetjmp(__mimemw_actjmp, 1))
1099 goto jleave;
1101 switch (convert) {
1102 case CONV_FROMQP:
1103 state = qp_decode(&out, &in, rest);
1104 goto jqpb64_dec;
1105 case CONV_TOQP:
1106 qp_encode(&out, &in, QP_NONE);
1107 goto jqpb64_enc;
1108 case CONV_8BIT:
1109 sz = quoteflt_push(qf, in.s, in.l);
1110 break;
1111 case CONV_FROMB64:
1112 rest = NULL;
1113 /* FALLTHRU */
1114 case CONV_FROMB64_T:
1115 state = b64_decode(&out, &in, rest);
1116 jqpb64_dec:
1117 if ((sz = out.l) != 0) {
1118 ui32_t opl = qf->qf_pfix_len;
1119 if (state != OKAY)
1120 qf->qf_pfix_len = 0;
1121 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), rest, qf);
1122 qf->qf_pfix_len = opl;
1124 if (state != OKAY)
1125 sz = -1;
1126 break;
1127 case CONV_TOB64:
1128 b64_encode(&out, &in, B64_LF | B64_MULTILINE);
1129 jqpb64_enc:
1130 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1131 if (sz != (ssize_t)out.l)
1132 sz = -1;
1133 break;
1134 case CONV_FROMHDR:
1135 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1136 sz = quoteflt_push(qf, out.s, out.l);
1137 break;
1138 case CONV_TOHDR:
1139 sz = mime_write_tohdr(&in, f);
1140 break;
1141 case CONV_TOHDR_A:
1142 sz = mime_write_tohdr_a(&in, f);
1143 break;
1144 default:
1145 sz = _fwrite_td(&in, dflags, NULL, qf);
1146 break;
1148 jleave:
1149 if (out.s != NULL)
1150 free(out.s);
1151 if (in.s != ptr)
1152 free(in.s);
1153 safe_signal(SIGPIPE, __mimemw_opipe);
1154 if (__mimemw_sig != 0)
1155 n_raise(__mimemw_sig);
1156 NYD_LEAVE;
1157 return sz;
1160 /* s-it-mode */