(BWDIC!) Rewrite prompt handling via new n_tty_create_prompt()..
[s-mailx.git] / mime.c
blobb4b8a8a6745201dc16f359faa927bc5f3c484904
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 + 4);
173 if(options & OPT_UNICODE){
174 out.s[out.l+0] = '\xEF'; /* TODO magic */
175 out.s[out.l+1] = '\xBF';
176 out.s[out.l+2] = '\xBD';
177 out.l += 3;
178 }else
179 out.s[out.l++] = '?';
180 } else
181 n_str_add(outrest, &in);
183 in = out;
184 out.l = 0;
185 out.s = NULL;
186 flags &= ~_TD_BUFCOPY;
188 if (buf != NULL)
189 free(buf);
191 #endif
193 if (flags & TD_ISPR)
194 makeprint(&in, &out);
195 else if (flags & _TD_BUFCOPY)
196 n_str_dup(&out, &in);
197 else
198 out = in;
199 if (flags & TD_DELCTRL)
200 out.l = delctrl(out.s, out.l);
202 __mimefwtd_sig = 0;
203 __mimefwtd_opipe = safe_signal(SIGPIPE, &__mimefwtd_onsig);
204 if (sigsetjmp(__mimefwtd_actjmp, 1)) {
205 rv = 0;
206 goto j__sig;
209 rv = quoteflt_push(qf, out.s, out.l);
211 j__sig:
212 if (out.s != in.s)
213 free(out.s);
214 if (in.s != input->s)
215 free(in.s);
216 safe_signal(SIGPIPE, __mimefwtd_opipe);
217 if (__mimefwtd_sig != 0)
218 n_raise(__mimefwtd_sig);
219 NYD_LEAVE;
220 return rv;
223 static ssize_t
224 mime_write_tohdr(struct str *in, FILE *fo)
226 /* TODO mime_write_tohdr(): we don't know the name of our header->maxcol..
227 * TODO MIME/send layer rewrite: more available state!!
228 * TODO Because of this we cannot make a difference in between structured
229 * TODO and unstructured headers (RFC 2047, 5. (2))
230 * TODO NOT MULTIBYTE SAFE IF AN ENCODED WORD HAS TO BE SPLITTED!
231 * TODO To be better we had to mbtowc_l() (non-std! and no locale!!) and
232 * TODO work char-wise! -> S-CText..
233 * TODO The real problem for STD compatibility is however that "in" is
234 * TODO already iconv(3) encoded to the target character set! We could
235 * TODO also solve it (very expensively!) if we would narrow down to an
236 * TODO encoded word and then iconv(3)+MIME encode in one go, in which
237 * TODO case multibyte errors could be catched! */
238 enum {
239 /* Maximum line length *//* XXX we are too inflexible and could use
240 * XXX MIME_LINELEN unless an RFC 2047 encoding was actually used */
241 _MAXCOL = MIME_LINELEN_RFC2047
243 enum {
244 _FIRST = 1<<0, /* Nothing written yet, start of string */
245 _NO_QP = 1<<1, /* No quoted-printable allowed */
246 _NO_B64 = 1<<2, /* Ditto, base64 */
247 _ENC_LAST = 1<<3, /* Last round generated encoded word */
248 _SHOULD_BEE = 1<<4, /* Avoid lines longer than SHOULD via encoding */
249 _RND_SHIFT = 5,
250 _RND_MASK = (1<<_RND_SHIFT) - 1,
251 _SPACE = 1<<(_RND_SHIFT+1), /* Leading whitespace */
252 _8BIT = 1<<(_RND_SHIFT+2), /* High bit set */
253 _ENCODE = 1<<(_RND_SHIFT+3), /* Need encoding */
254 _ENC_B64 = 1<<(_RND_SHIFT+4), /* - let it be base64 */
255 _OVERLONG = 1<<(_RND_SHIFT+5) /* Temporarily rised limit */
256 } flags = _FIRST;
258 struct str cout, cin;
259 char const *cset7, *cset8, *wbot, *upper, *wend, *wcur;
260 ui32_t cset7_len, cset8_len;
261 size_t col, i, j;
262 ssize_t sz;
263 NYD_ENTER;
265 cout.s = NULL, cout.l = 0;
266 cset7 = charset_get_7bit();
267 cset7_len = (ui32_t)strlen(cset7);
268 cset8 = _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */
269 cset8_len = (ui32_t)strlen(cset8);
271 /* RFC 1468, "MIME Considerations":
272 * ISO-2022-JP may also be used in MIME Part 2 headers. The "B"
273 * encoding should be used with ISO-2022-JP text. */
274 /* TODO of course, our current implementation won't deal properly with
275 * TODO any stateful encoding at all... (the standard says each encoded
276 * TODO word must include all necessary reset sequences..., i.e., each
277 * TODO encoded word must be a self-contained iconv(3) life cycle) */
278 if (!asccasecmp(cset8, "iso-2022-jp"))
279 flags |= _NO_QP;
281 wbot = in->s;
282 upper = wbot + in->l;
283 col = sizeof("Mail-Followup-To: ") -1; /* dreadful thing */
285 for (sz = 0; wbot < upper; flags &= ~_FIRST, wbot = wend) {
286 flags &= _RND_MASK;
287 wcur = wbot;
288 while (wcur < upper && whitechar(*wcur)) {
289 flags |= _SPACE;
290 ++wcur;
293 /* Any occurrence of whitespace resets prevention of lines >SHOULD via
294 * enforced encoding (xxx SHOULD, but.. encoding is expensive!!) */
295 if (flags & _SPACE)
296 flags &= ~_SHOULD_BEE;
298 /* Data ends with WS - dump it and done.
299 * Also, if we have seen multiple successive whitespace characters, then
300 * if there was no encoded word last, i.e., if we can simply take them
301 * over to the output as-is, keep one WS for possible later separation
302 * purposes and simply print the others as-is, directly! */
303 if (wcur == upper) {
304 wend = wcur;
305 goto jnoenc_putws;
307 if ((flags & (_ENC_LAST | _SPACE)) == _SPACE && wcur - wbot > 1) {
308 wend = wcur - 1;
309 goto jnoenc_putws;
312 /* Skip over a word to next non-whitespace, keep track along the way
313 * whether our 7-bit charset suffices to represent the data */
314 for (wend = wcur; wend < upper; ++wend) {
315 if (whitechar(*wend))
316 break;
317 if ((uc_i)*wend & 0x80)
318 flags |= _8BIT;
321 /* Decide whether the range has to become encoded or not */
322 i = PTR2SIZE(wend - wcur);
323 j = mime_enc_mustquote(wcur, i, MIMEEF_ISHEAD);
324 /* If it just cannot fit on a SHOULD line length, force encode */
325 if (i >= _MAXCOL) {
326 flags |= _SHOULD_BEE; /* (Sigh: SHOULD only, not MUST..) */
327 goto j_beejump;
329 if ((flags & _SHOULD_BEE) || j > 0) {
330 j_beejump:
331 flags |= _ENCODE;
332 /* Use base64 if requested or more than 50% -37.5-% of the bytes of
333 * the string need to be encoded */
334 if ((flags & _NO_QP) || j >= i >> 1)/*(i >> 2) + (i >> 3))*/
335 flags |= _ENC_B64;
337 DBG( if (flags & _8BIT) assert(flags & _ENCODE); )
339 if (!(flags & _ENCODE)) {
340 /* Encoded word produced, but no linear whitespace for necessary RFC
341 * 2047 separation? Generate artificial data (bad standard!) */
342 if ((flags & (_ENC_LAST | _SPACE)) == _ENC_LAST) {
343 if (col >= _MAXCOL) {
344 putc('\n', fo);
345 ++sz;
346 col = 0;
348 putc(' ', fo);
349 ++sz;
350 ++col;
353 jnoenc_putws:
354 flags &= ~_ENC_LAST;
356 /* todo No effort here: (1) v15.0 has to bring complete rewrite,
357 * todo (2) the standard is braindead and (3) usually this is one
358 * todo word only, and why be smarter than the standard? */
359 jnoenc_retry:
360 i = PTR2SIZE(wend - wbot);
361 if (i + col <= (flags & _OVERLONG ? MIME_LINELEN_MAX : _MAXCOL)) {
362 i = fwrite(wbot, sizeof *wbot, i, fo);
363 sz += i;
364 col += i;
365 continue;
368 /* Doesn't fit, try to break the line first; */
369 if (col > 1) {
370 putc('\n', fo);
371 if (whitechar(*wbot)) {
372 putc((uc_i)*wbot, fo);
373 ++wbot;
374 } else
375 putc(' ', fo); /* Bad standard: artificial data! */
376 sz += 2;
377 col = 1;
378 flags |= _OVERLONG;
379 goto jnoenc_retry;
382 /* It is so long that it needs to be broken, effectively causing
383 * artificial spaces to be inserted (bad standard), yuck */
384 /* todo This is not multibyte safe, as above; and completely stupid
385 * todo P.S.: our _SHOULD_BEE prevents these cases in the meanwhile */
386 /* FIXME OPT_UNICODE and parse using UTF-8 sync possibility! */
387 wcur = wbot + MIME_LINELEN_MAX - 8;
388 while (wend > wcur)
389 wend -= 4;
390 goto jnoenc_retry;
391 } else {
392 /* Encoding to encoded word(s); deal with leading whitespace, place
393 * a separator first as necessary: encoded words must always be
394 * separated from text and other encoded words with linear WS.
395 * And if an encoded word was last, intermediate whitespace must
396 * also be encoded, otherwise it would get stripped away! */
397 wcur = n_UNCONST("");
398 if ((flags & (_ENC_LAST | _SPACE)) != _SPACE) {
399 /* Reinclude whitespace */
400 flags &= ~_SPACE;
401 /* We don't need to place a separator at the very beginning */
402 if (!(flags & _FIRST))
403 wcur = n_UNCONST(" ");
404 } else
405 wcur = wbot++;
407 flags |= _ENC_LAST;
408 pstate |= PS_HEADER_NEEDED_MIME;
410 /* RFC 2047:
411 * An 'encoded-word' may not be more than 75 characters long,
412 * including 'charset', 'encoding', 'encoded-text', and
413 * delimiters. If it is desirable to encode more text than will
414 * fit in an 'encoded-word' of 75 characters, multiple
415 * 'encoded-word's (separated by CRLF SPACE) may be used.
417 * While there is no limit to the length of a multiple-line
418 * header field, each line of a header field that contains one
419 * or more 'encoded-word's is limited to 76 characters */
420 jenc_retry:
421 cin.s = n_UNCONST(wbot);
422 cin.l = PTR2SIZE(wend - wbot);
424 /* C99 */{
425 struct str *xout;
427 if(flags & _ENC_B64)
428 xout = b64_encode(&cout, &cin, B64_ISHEAD | B64_ISENCWORD);
429 else
430 xout = qp_encode(&cout, &cin, QP_ISHEAD | QP_ISENCWORD);
431 if(xout == NULL){
432 sz = -1;
433 break;
435 j = xout->l;
437 /* (Avoid trigraphs in the RFC 2047 placeholder..) */
438 i = j + (flags & _8BIT ? cset8_len : cset7_len) + sizeof("=!!B!!=") -1;
439 if (*wcur != '\0')
440 ++i;
442 jenc_retry_same:
443 /* Unfortunately RFC 2047 explicitly disallows encoded words to be
444 * longer (just like RFC 5322's "a line SHOULD fit in 78 but MAY be
445 * 998 characters long"), so we cannot use the _OVERLONG mechanism,
446 * even though all tested mailers seem to support it */
447 if (i + col <= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ _MAXCOL)) {
448 fprintf(fo, "%.1s=?%s?%c?%.*s?=",
449 wcur, (flags & _8BIT ? cset8 : cset7),
450 (flags & _ENC_B64 ? 'B' : 'Q'),
451 (int)cout.l, cout.s);
452 sz += i;
453 col += i;
454 continue;
457 /* Doesn't fit, try to break the line first */
458 /* TODO I've commented out the _FIRST test since we (1) cannot do
459 * TODO _OVERLONG since (MUAs support but) the standard disallows,
460 * TODO and because of our iconv problem i prefer an empty first line
461 * TODO in favour of a possibly messed up multibytes character. :-( */
462 if (col > 1 /* TODO && !(flags & _FIRST)*/) {
463 putc('\n', fo);
464 sz += 2;
465 col = 1;
466 if (!(flags & _SPACE)) {
467 putc(' ', fo);
468 wcur = n_UNCONST("");
469 /*flags |= _OVERLONG;*/
470 goto jenc_retry_same;
471 } else {
472 putc((uc_i)*wcur, fo);
473 if (whitechar(*(wcur = wbot)))
474 ++wbot;
475 else {
476 flags &= ~_SPACE;
477 wcur = n_UNCONST("");
479 /*flags &= ~_OVERLONG;*/
480 goto jenc_retry;
484 /* It is so long that it needs to be broken, effectively causing
485 * artificial data to be inserted (bad standard), yuck */
486 /* todo This is not multibyte safe, as above */
487 /*if (!(flags & _OVERLONG)) { Mechanism explicitly forbidden by 2047
488 flags |= _OVERLONG;
489 goto jenc_retry;
492 /* FIXME OPT_UNICODE and parse using UTF-8 sync possibility! */
493 i = PTR2SIZE(wend - wbot) + !!(flags & _SPACE);
494 j = 3 + !(flags & _ENC_B64);
495 for (;;) {
496 wend -= j;
497 i -= j;
498 /* (Note the problem most likely is the transfer-encoding blow,
499 * which is why we test this *after* the decrements.. */
500 if (i <= _MAXCOL)
501 break;
503 goto jenc_retry;
507 if (cout.s != NULL)
508 free(cout.s);
509 NYD_LEAVE;
510 return sz;
513 static ssize_t
514 convhdra(char const *str, size_t len, FILE *fp)
516 #ifdef HAVE_ICONV
517 struct str ciconv;
518 #endif
519 struct str cin;
520 ssize_t ret = 0;
521 NYD_ENTER;
523 cin.s = n_UNCONST(str);
524 cin.l = len;
525 #ifdef HAVE_ICONV
526 ciconv.s = NULL;
527 if (iconvd != (iconv_t)-1) {
528 ciconv.l = 0;
529 if(n_iconv_str(iconvd, n_ICONV_IGN_NOREVERSE, &ciconv, &cin, NULL) != 0){
530 n_iconv_reset(iconvd);
531 goto jleave;
533 cin = ciconv;
535 #endif
536 ret = mime_write_tohdr(&cin, fp);
537 #ifdef HAVE_ICONV
538 jleave:
539 if (ciconv.s != NULL)
540 free(ciconv.s);
541 #endif
542 NYD_LEAVE;
543 return ret;
546 static ssize_t
547 mime_write_tohdr_a(struct str *in, FILE *f) /* TODO error handling */
549 char const *cp, *lastcp;
550 ssize_t sz, x;
551 NYD_ENTER;
553 in->s[in->l] = '\0';
554 lastcp = in->s;
555 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
556 if ((sz = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0)
557 goto jleave;
558 lastcp = cp;
559 } else {
560 cp = in->s;
561 sz = 0;
564 for ( ; *cp != '\0'; ++cp) {
565 switch (*cp) {
566 case '(':
567 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp + 1), f);
568 lastcp = ++cp;
569 cp = skip_comment(cp);
570 if (--cp > lastcp) {
571 if ((x = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0) {
572 sz = x;
573 goto jleave;
575 sz += x;
577 lastcp = cp;
578 break;
579 case '"':
580 while (*cp) {
581 if (*++cp == '"')
582 break;
583 if (*cp == '\\' && cp[1] != '\0')
584 ++cp;
586 break;
589 if (cp > lastcp)
590 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp), f);
591 jleave:
592 NYD_LEAVE;
593 return sz;
596 static void
597 _append_str(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
599 NYD_ENTER;
600 *buf = srealloc(*buf, *sz += len);
601 memcpy(&(*buf)[*pos], str, len);
602 *pos += len;
603 NYD_LEAVE;
606 static void
607 _append_conv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
609 struct str in, out;
610 NYD_ENTER;
612 in.s = n_UNCONST(str);
613 in.l = len;
614 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
615 _append_str(buf, sz, pos, out.s, out.l);
616 free(out.s);
617 NYD_LEAVE;
620 FL char const *
621 charset_get_7bit(void)
623 char const *t;
624 NYD_ENTER;
626 if ((t = ok_vlook(charset_7bit)) == NULL)
627 t = CHARSET_7BIT;
628 NYD_LEAVE;
629 return t;
632 #ifdef HAVE_ICONV
633 FL char const *
634 charset_get_8bit(void)
636 char const *t;
637 NYD_ENTER;
639 if ((t = ok_vlook(CHARSET_8BIT_OKEY)) == NULL)
640 t = CHARSET_8BIT;
641 NYD_LEAVE;
642 return t;
644 #endif
646 FL char const *
647 charset_get_lc(void)
649 char const *t;
650 NYD_ENTER;
652 if ((t = ok_vlook(ttycharset)) == NULL)
653 t = CHARSET_8BIT;
654 NYD_LEAVE;
655 return t;
658 FL bool_t
659 charset_iter_reset(char const *a_charset_to_try_first)
661 char const *sarr[3];
662 size_t sarrl[3], len;
663 char *cp;
664 NYD_ENTER;
665 n_UNUSED(a_charset_to_try_first);
667 #ifdef HAVE_ICONV
668 sarr[0] = a_charset_to_try_first;
669 if ((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
670 ok_blook(sendcharsets_else_ttycharset))
671 sarr[1] = charset_get_lc();
672 sarr[2] = charset_get_8bit();
673 #else
674 sarr[2] = charset_get_lc();
675 #endif
677 sarrl[2] = len = strlen(sarr[2]);
678 #ifdef HAVE_ICONV
679 if ((cp = n_UNCONST(sarr[1])) != NULL)
680 len += (sarrl[1] = strlen(cp));
681 else
682 sarrl[1] = 0;
683 if ((cp = n_UNCONST(sarr[0])) != NULL)
684 len += (sarrl[0] = strlen(cp));
685 else
686 sarrl[0] = 0;
687 #endif
689 _cs_iter_base = cp = salloc(len + 1 + 1 +1);
691 #ifdef HAVE_ICONV
692 if ((len = sarrl[0]) != 0) {
693 memcpy(cp, sarr[0], len);
694 cp[len] = ',';
695 cp += ++len;
697 if ((len = sarrl[1]) != 0) {
698 memcpy(cp, sarr[1], len);
699 cp[len] = ',';
700 cp += ++len;
702 #endif
703 len = sarrl[2];
704 memcpy(cp, sarr[2], len);
705 cp[len] = '\0';
707 _CS_ITER_STEP();
708 NYD_LEAVE;
709 return (_cs_iter != NULL);
712 FL bool_t
713 charset_iter_next(void)
715 bool_t rv;
716 NYD_ENTER;
718 _CS_ITER_STEP();
719 rv = (_cs_iter != NULL);
720 NYD_LEAVE;
721 return rv;
724 FL bool_t
725 charset_iter_is_valid(void)
727 bool_t rv;
728 NYD_ENTER;
730 rv = (_cs_iter != NULL);
731 NYD_LEAVE;
732 return rv;
735 FL char const *
736 charset_iter(void)
738 char const *rv;
739 NYD_ENTER;
741 rv = _cs_iter;
742 NYD_LEAVE;
743 return rv;
746 FL char const *
747 charset_iter_or_fallback(void)
749 char const *rv;
750 NYD_ENTER;
752 rv = _CS_ITER_GET();
753 NYD_LEAVE;
754 return rv;
757 FL void
758 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
760 NYD_ENTER;
761 outer_storage[0] = _cs_iter_base;
762 outer_storage[1] = _cs_iter;
763 NYD_LEAVE;
766 FL void
767 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
769 NYD_ENTER;
770 _cs_iter_base = outer_storage[0];
771 _cs_iter = outer_storage[1];
772 NYD_LEAVE;
775 #ifdef HAVE_ICONV
776 FL char const *
777 need_hdrconv(struct header *hp) /* TODO once only, then iter */
779 struct n_header_field *hfp;
780 char const *rv;
781 NYD_ENTER;
783 rv = NULL;
785 if((hfp = hp->h_user_headers) != NULL)
786 do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
787 goto jneeds;
788 while((hfp = hfp->hf_next) != NULL);
790 if((hfp = hp->h_custom_headers) != NULL ||
791 (hp->h_custom_headers = hfp = n_customhdr_query()) != NULL)
792 do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
793 goto jneeds;
794 while((hfp = hfp->hf_next) != NULL);
796 if (hp->h_mft != NULL) {
797 if (_name_highbit(hp->h_mft))
798 goto jneeds;
800 if (hp->h_from != NULL) {
801 if (_name_highbit(hp->h_from))
802 goto jneeds;
803 } else if (_has_highbit(myaddrs(NULL)))
804 goto jneeds;
805 if (hp->h_replyto) {
806 if (_name_highbit(hp->h_replyto))
807 goto jneeds;
808 } else if (_has_highbit(ok_vlook(replyto)))
809 goto jneeds;
810 if (hp->h_sender) {
811 if (_name_highbit(hp->h_sender))
812 goto jneeds;
813 } else if (_has_highbit(ok_vlook(sender)))
814 goto jneeds;
816 if (_name_highbit(hp->h_to))
817 goto jneeds;
818 if (_name_highbit(hp->h_cc))
819 goto jneeds;
820 if (_name_highbit(hp->h_bcc))
821 goto jneeds;
822 if (_has_highbit(hp->h_subject))
823 jneeds:
824 rv = _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
825 NYD_LEAVE;
826 return rv;
828 #endif /* HAVE_ICONV */
830 FL void
831 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
833 /* TODO mime_fromhdr(): is called with strings that contain newlines;
834 * TODO this is the usual newline problem all around the codebase;
835 * TODO i.e., if we strip it, then the display misses it ;>
836 * TODO this is why it is so messy and why S-nail v14.2 plus additional
837 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
838 * TODO why our display reflects what is contained in the message: the 1:1
839 * TODO relationship of message content and display!
840 * TODO instead a header line should be decoded to what it is (a single
841 * TODO line that is) and it should be objective to the backend whether
842 * TODO it'll be folded to fit onto the display or not, e.g., for search
843 * TODO purposes etc. then the only condition we have to honour in here
844 * TODO is that whitespace in between multiple adjacent MIME encoded words
845 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
846 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
847 struct str cin, cout;
848 char *p, *op, *upper;
849 ui32_t convert, lastenc, lastoutl;
850 #ifdef HAVE_ICONV
851 char const *tcs;
852 char *cbeg;
853 iconv_t fhicd = (iconv_t)-1;
854 #endif
855 NYD_ENTER;
857 out->l = 0;
858 if (in->l == 0) {
859 *(out->s = smalloc(1)) = '\0';
860 goto jleave;
862 out->s = NULL;
864 #ifdef HAVE_ICONV
865 tcs = charset_get_lc();
866 #endif
867 p = in->s;
868 upper = p + in->l;
869 lastenc = lastoutl = 0;
871 while (p < upper) {
872 op = p;
873 if (*p == '=' && *(p + 1) == '?') {
874 p += 2;
875 #ifdef HAVE_ICONV
876 cbeg = p;
877 #endif
878 while (p < upper && *p != '?')
879 ++p; /* strip charset */
880 if (p >= upper)
881 goto jnotmime;
882 ++p;
883 #ifdef HAVE_ICONV
884 if (flags & TD_ICONV) {
885 size_t i = PTR2SIZE(p - cbeg);
886 char *ltag, *cs = ac_alloc(i);
888 memcpy(cs, cbeg, --i);
889 cs[i] = '\0';
890 /* RFC 2231 extends the RFC 2047 character set definition in
891 * encoded words by language tags - silently strip those off */
892 if ((ltag = strchr(cs, '*')) != NULL)
893 *ltag = '\0';
895 if (fhicd != (iconv_t)-1)
896 n_iconv_close(fhicd);
897 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
898 ac_free(cs);
900 #endif
901 switch (*p) {
902 case 'B': case 'b':
903 convert = CONV_FROMB64;
904 break;
905 case 'Q': case 'q':
906 convert = CONV_FROMQP;
907 break;
908 default: /* invalid, ignore */
909 goto jnotmime;
911 if (*++p != '?')
912 goto jnotmime;
913 cin.s = ++p;
914 cin.l = 1;
915 for (;;) {
916 if (PTRCMP(p + 1, >=, upper))
917 goto jnotmime;
918 if (*p++ == '?' && *p == '=')
919 break;
920 ++cin.l;
922 ++p;
923 --cin.l;
925 cout.s = NULL;
926 cout.l = 0;
927 if (convert == CONV_FROMB64) {
928 if(!b64_decode_header(&cout, &cin))
929 n_str_assign_cp(&cout, _("[Invalid Base64 encoding]"));
930 }else if(!qp_decode_header(&cout, &cin))
931 n_str_assign_cp(&cout, _("[Invalid Quoted-Printable encoding]"));
933 out->l = lastenc;
934 #ifdef HAVE_ICONV
935 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
936 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
937 convert = n_iconv_str(fhicd, n_ICONV_UNIDEFAULT, &cin, &cout, NULL);
938 out = n_str_add(out, &cin);
939 if (convert) {/* EINVAL at EOS */
940 n_iconv_reset(fhicd);
941 out = n_str_add_buf(out, "?", 1); /* TODO unicode replacement */
943 free(cin.s);
944 } else
945 #endif
946 out = n_str_add(out, &cout);
947 lastenc = lastoutl = out->l;
948 free(cout.s);
949 } else
950 jnotmime: {
951 bool_t onlyws;
953 p = op;
954 onlyws = (lastenc > 0);
955 for (;;) {
956 if (++op == upper)
957 break;
958 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
959 break;
960 if (onlyws && !blankchar(*op))
961 onlyws = FAL0;
964 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
965 p = op;
966 if (!onlyws || lastoutl != lastenc)
967 lastenc = out->l;
968 lastoutl = out->l;
971 out->s[out->l] = '\0';
973 if (flags & TD_ISPR) {
974 makeprint(out, &cout);
975 free(out->s);
976 *out = cout;
978 if (flags & TD_DELCTRL)
979 out->l = delctrl(out->s, out->l);
980 #ifdef HAVE_ICONV
981 if (fhicd != (iconv_t)-1)
982 n_iconv_close(fhicd);
983 #endif
984 jleave:
985 NYD_LEAVE;
986 return;
989 FL char *
990 mime_fromaddr(char const *name)
992 char const *cp, *lastcp;
993 char *res = NULL;
994 size_t ressz = 1, rescur = 0;
995 NYD_ENTER;
997 if (name == NULL)
998 goto jleave;
999 if (*name == '\0') {
1000 res = savestr(name);
1001 goto jleave;
1004 if ((cp = routeaddr(name)) != NULL && cp > name) {
1005 _append_conv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
1006 lastcp = cp;
1007 } else
1008 cp = lastcp = name;
1010 for ( ; *cp; ++cp) {
1011 switch (*cp) {
1012 case '(':
1013 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
1014 lastcp = ++cp;
1015 cp = skip_comment(cp);
1016 if (--cp > lastcp)
1017 _append_conv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1018 lastcp = cp;
1019 break;
1020 case '"':
1021 while (*cp) {
1022 if (*++cp == '"')
1023 break;
1024 if (*cp == '\\' && cp[1] != '\0')
1025 ++cp;
1027 break;
1030 if (cp > lastcp)
1031 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1032 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1033 if (rescur == 0)
1034 res = n_UNCONST("");
1035 else
1036 res[rescur] = '\0';
1037 { char *x = res;
1038 res = savestr(res);
1039 free(x);
1041 jleave:
1042 NYD_LEAVE;
1043 return res;
1046 FL ssize_t
1047 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1048 enum tdflags dflags)
1050 ssize_t rv;
1051 struct quoteflt *qf;
1052 NYD_ENTER;
1054 quoteflt_reset(qf = quoteflt_dummy(), f);
1055 rv = mime_write(ptr, size, f, convert, dflags, qf, NULL, NULL);
1056 quoteflt_flush(qf);
1057 NYD_LEAVE;
1058 return rv;
1061 static sigjmp_buf __mimemw_actjmp; /* TODO someday.. */
1062 static int __mimemw_sig; /* TODO someday.. */
1063 static sighandler_type __mimemw_opipe;
1064 static void
1065 __mimemw_onsig(int sig) /* TODO someday, we won't need it no more */
1067 NYD_X; /* Signal handler */
1068 __mimemw_sig = sig;
1069 siglongjmp(__mimemw_actjmp, 1);
1072 FL ssize_t
1073 mime_write(char const *ptr, size_t size, FILE *f,
1074 enum conversion convert, enum tdflags volatile dflags,
1075 struct quoteflt *qf, struct str * volatile outrest,
1076 struct str * volatile inrest)
1078 /* TODO note: after send/MIME layer rewrite we will have a string pool
1079 * TODO so that memory allocation count drops down massively; for now,
1080 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1081 struct str in, out;
1082 ssize_t volatile sz;
1083 NYD_ENTER;
1085 dflags |= _TD_BUFCOPY;
1087 in.s = n_UNCONST(ptr);
1088 in.l = size;
1089 if(inrest != NULL && inrest->l > 0){
1090 out.s = smalloc(inrest->l + size + 1);
1091 memcpy(out.s, inrest->s, inrest->l);
1092 if(size > 0)
1093 memcpy(&out.s[inrest->l], in.s, size);
1094 size += inrest->l;
1095 inrest->l = 0;
1096 (in.s = out.s)[in.l = size] = '\0';
1097 dflags &= ~_TD_BUFCOPY;
1100 out.s = NULL;
1101 out.l = 0;
1103 if ((sz = size) == 0) {
1104 if (outrest != NULL && outrest->l != 0)
1105 goto jconvert;
1106 goto jleave;
1109 #ifdef HAVE_ICONV
1110 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1111 (convert == CONV_TOQP || convert == CONV_8BIT ||
1112 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1113 if (n_iconv_str(iconvd, n_ICONV_IGN_NOREVERSE, &out, &in, NULL) != 0) {
1114 n_iconv_reset(iconvd);
1115 /* TODO This causes hard-failure. We would need to have an action
1116 * TODO policy FAIL|IGNORE|SETERROR(but continue). Better huh? */
1117 sz = -1;
1118 goto jleave;
1120 in = out;
1121 out.s = NULL;
1122 dflags &= ~_TD_BUFCOPY;
1124 #endif
1126 jconvert:
1127 __mimemw_sig = 0;
1128 __mimemw_opipe = safe_signal(SIGPIPE, &__mimemw_onsig);
1129 if (sigsetjmp(__mimemw_actjmp, 1))
1130 goto jleave;
1132 switch (convert) {
1133 case CONV_FROMQP:
1134 if(!qp_decode_text(&out, &in, outrest, inrest)){
1135 n_err(_("Invalid Quoted-Printable encoding ignored\n"));
1136 sz = 0; /* TODO sz = -1 stops outer levels! */
1137 break;
1139 goto jqpb64_dec;
1140 case CONV_TOQP:
1141 if(qp_encode(&out, &in, QP_NONE) == NULL){
1142 sz = 0; /* TODO sz = -1 stops outer levels! */
1143 break;
1145 goto jqpb64_enc;
1146 case CONV_8BIT:
1147 sz = quoteflt_push(qf, in.s, in.l);
1148 break;
1149 case CONV_FROMB64:
1150 if(!b64_decode_text(&out, &in, outrest, inrest))
1151 goto jeb64;
1152 outrest = NULL;
1153 if(0){
1154 /* FALLTHRU */
1155 case CONV_FROMB64_T:
1156 if(!b64_decode_text(&out, &in, outrest, inrest)){
1157 jeb64:
1158 n_err(_("Invalid Base64 encoding ignored\n"));
1159 sz = 0; /* TODO sz = -1 stops outer levels! */
1160 break;
1163 jqpb64_dec:
1164 if ((sz = out.l) != 0) {
1165 ui32_t opl = qf->qf_pfix_len;
1166 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), outrest, qf);
1167 qf->qf_pfix_len = opl;
1169 break;
1170 case CONV_TOB64:
1171 if(b64_encode(&out, &in, B64_LF | B64_MULTILINE) == NULL){
1172 sz = -1;
1173 break;
1175 jqpb64_enc:
1176 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1177 if (sz != (ssize_t)out.l)
1178 sz = -1;
1179 break;
1180 case CONV_FROMHDR:
1181 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1182 sz = quoteflt_push(qf, out.s, out.l);
1183 break;
1184 case CONV_TOHDR:
1185 sz = mime_write_tohdr(&in, f);
1186 break;
1187 case CONV_TOHDR_A:
1188 sz = mime_write_tohdr_a(&in, f);
1189 break;
1190 default:
1191 sz = _fwrite_td(&in, dflags, NULL, qf);
1192 break;
1194 jleave:
1195 if (out.s != NULL)
1196 free(out.s);
1197 if (in.s != ptr)
1198 free(in.s);
1199 safe_signal(SIGPIPE, __mimemw_opipe);
1200 if (__mimemw_sig != 0)
1201 n_raise(__mimemw_sig);
1202 NYD_LEAVE;
1203 return sz;
1206 /* s-it-mode */