cc-test.sh: do a simple \`Resend' test
[s-mailx.git] / mime.c
blobab2c3bb07d2505e27b18f898c1f517a73c214275
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;
405 /* RFC 2047:
406 * An 'encoded-word' may not be more than 75 characters long,
407 * including 'charset', 'encoding', 'encoded-text', and
408 * delimiters. If it is desirable to encode more text than will
409 * fit in an 'encoded-word' of 75 characters, multiple
410 * 'encoded-word's (separated by CRLF SPACE) may be used.
412 * While there is no limit to the length of a multiple-line
413 * header field, each line of a header field that contains one
414 * or more 'encoded-word's is limited to 76 characters */
415 jenc_retry:
416 cin.s = UNCONST(wbot);
417 cin.l = PTR2SIZE(wend - wbot);
419 if (flags & _ENC_B64)
420 j = b64_encode(&cout, &cin, B64_ISHEAD | B64_ISENCWORD)->l;
421 else
422 j = qp_encode(&cout, &cin, QP_ISHEAD | QP_ISENCWORD)->l;
423 /* (Avoid trigraphs in the RFC 2047 placeholder..) */
424 i = j + (flags & _8BIT ? cset8_len : cset7_len) + sizeof("=!!B!!=") -1;
425 if (*wcur != '\0')
426 ++i;
428 jenc_retry_same:
429 /* Unfortunately RFC 2047 explicitly disallows encoded words to be
430 * longer (just like RFC 5322's "a line SHOULD fit in 78 but MAY be
431 * 998 characters long"), so we cannot use the _OVERLONG mechanism,
432 * even though all tested mailers seem to support it */
433 if (i + col <= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ _MAXCOL)) {
434 fprintf(fo, "%.1s=?%s?%c?%.*s?=",
435 wcur, (flags & _8BIT ? cset8 : cset7),
436 (flags & _ENC_B64 ? 'B' : 'Q'),
437 (int)cout.l, cout.s);
438 sz += i;
439 col += i;
440 continue;
443 /* Doesn't fit, try to break the line first */
444 /* TODO I've commented out the _FIRST test since we (1) cannot do
445 * TODO _OVERLONG since (MUAs support but) the standard disallows,
446 * TODO and because of our iconv problem i prefer an empty first line
447 * TODO in favour of a possibly messed up multibytes character. :-( */
448 if (col > 1 /* TODO && !(flags & _FIRST)*/) {
449 putc('\n', fo);
450 sz += 2;
451 col = 1;
452 if (!(flags & _SPACE)) {
453 putc(' ', fo);
454 wcur = UNCONST("");
455 /*flags |= _OVERLONG;*/
456 goto jenc_retry_same;
457 } else {
458 putc((uc_i)*wcur, fo);
459 if (whitechar(*(wcur = wbot)))
460 ++wbot;
461 else {
462 flags &= ~_SPACE;
463 wcur = UNCONST("");
465 /*flags &= ~_OVERLONG;*/
466 goto jenc_retry;
470 /* It is so long that it needs to be broken, effectively causing
471 * artificial data to be inserted (bad standard), yuck */
472 /* todo This is not multibyte safe, as above */
473 /*if (!(flags & _OVERLONG)) { Mechanism explicitly forbidden by 2047
474 flags |= _OVERLONG;
475 goto jenc_retry;
478 /* FIXME OPT_UNICODE and parse using UTF-8 sync possibility! */
479 i = PTR2SIZE(wend - wbot) + !!(flags & _SPACE);
480 j = 3 + !(flags & _ENC_B64);
481 for (;;) {
482 wend -= j;
483 i -= j;
484 /* (Note the problem most likely is the transfer-encoding blow,
485 * which is why we test this *after* the decrements.. */
486 if (i <= _MAXCOL)
487 break;
489 goto jenc_retry;
493 if (cout.s != NULL)
494 free(cout.s);
495 NYD_LEAVE;
496 return sz;
499 static ssize_t
500 convhdra(char const *str, size_t len, FILE *fp)
502 #ifdef HAVE_ICONV
503 struct str ciconv;
504 #endif
505 struct str cin;
506 ssize_t ret = 0;
507 NYD_ENTER;
509 cin.s = UNCONST(str);
510 cin.l = len;
511 #ifdef HAVE_ICONV
512 ciconv.s = NULL;
513 if (iconvd != (iconv_t)-1) {
514 ciconv.l = 0;
515 if (n_iconv_str(iconvd, &ciconv, &cin, NULL, FAL0) != 0) {
516 n_iconv_reset(iconvd);
517 goto jleave;
519 cin = ciconv;
521 #endif
522 ret = mime_write_tohdr(&cin, fp);
523 #ifdef HAVE_ICONV
524 jleave:
525 if (ciconv.s != NULL)
526 free(ciconv.s);
527 #endif
528 NYD_LEAVE;
529 return ret;
532 static ssize_t
533 mime_write_tohdr_a(struct str *in, FILE *f) /* TODO error handling */
535 char const *cp, *lastcp;
536 ssize_t sz, x;
537 NYD_ENTER;
539 in->s[in->l] = '\0';
540 lastcp = in->s;
541 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
542 if ((sz = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0)
543 goto jleave;
544 lastcp = cp;
545 } else {
546 cp = in->s;
547 sz = 0;
550 for ( ; *cp != '\0'; ++cp) {
551 switch (*cp) {
552 case '(':
553 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp + 1), f);
554 lastcp = ++cp;
555 cp = skip_comment(cp);
556 if (--cp > lastcp) {
557 if ((x = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0) {
558 sz = x;
559 goto jleave;
561 sz += x;
563 lastcp = cp;
564 break;
565 case '"':
566 while (*cp) {
567 if (*++cp == '"')
568 break;
569 if (*cp == '\\' && cp[1] != '\0')
570 ++cp;
572 break;
575 if (cp > lastcp)
576 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp), f);
577 jleave:
578 NYD_LEAVE;
579 return sz;
582 static void
583 _append_str(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
585 NYD_ENTER;
586 *buf = srealloc(*buf, *sz += len);
587 memcpy(&(*buf)[*pos], str, len);
588 *pos += len;
589 NYD_LEAVE;
592 static void
593 _append_conv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
595 struct str in, out;
596 NYD_ENTER;
598 in.s = UNCONST(str);
599 in.l = len;
600 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
601 _append_str(buf, sz, pos, out.s, out.l);
602 free(out.s);
603 NYD_LEAVE;
606 FL char const *
607 charset_get_7bit(void)
609 char const *t;
610 NYD_ENTER;
612 if ((t = ok_vlook(charset_7bit)) == NULL)
613 t = CHARSET_7BIT;
614 NYD_LEAVE;
615 return t;
618 #ifdef HAVE_ICONV
619 FL char const *
620 charset_get_8bit(void)
622 char const *t;
623 NYD_ENTER;
625 if ((t = ok_vlook(CHARSET_8BIT_OKEY)) == NULL)
626 t = CHARSET_8BIT;
627 NYD_LEAVE;
628 return t;
630 #endif
632 FL char const *
633 charset_get_lc(void)
635 char const *t;
636 NYD_ENTER;
638 if ((t = ok_vlook(ttycharset)) == NULL)
639 t = CHARSET_8BIT;
640 NYD_LEAVE;
641 return t;
644 FL bool_t
645 charset_iter_reset(char const *a_charset_to_try_first)
647 char const *sarr[3];
648 size_t sarrl[3], len;
649 char *cp;
650 NYD_ENTER;
651 UNUSED(a_charset_to_try_first);
653 #ifdef HAVE_ICONV
654 sarr[0] = a_charset_to_try_first;
655 if ((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
656 ok_blook(sendcharsets_else_ttycharset))
657 sarr[1] = charset_get_lc();
658 sarr[2] = charset_get_8bit();
659 #else
660 sarr[2] = charset_get_lc();
661 #endif
663 sarrl[2] = len = strlen(sarr[2]);
664 #ifdef HAVE_ICONV
665 if ((cp = UNCONST(sarr[1])) != NULL)
666 len += (sarrl[1] = strlen(cp));
667 else
668 sarrl[1] = 0;
669 if ((cp = UNCONST(sarr[0])) != NULL)
670 len += (sarrl[0] = strlen(cp));
671 else
672 sarrl[0] = 0;
673 #endif
675 _cs_iter_base = cp = salloc(len + 1 + 1 +1);
677 #ifdef HAVE_ICONV
678 if ((len = sarrl[0]) != 0) {
679 memcpy(cp, sarr[0], len);
680 cp[len] = ',';
681 cp += ++len;
683 if ((len = sarrl[1]) != 0) {
684 memcpy(cp, sarr[1], len);
685 cp[len] = ',';
686 cp += ++len;
688 #endif
689 len = sarrl[2];
690 memcpy(cp, sarr[2], len);
691 cp[len] = '\0';
693 _CS_ITER_STEP();
694 NYD_LEAVE;
695 return (_cs_iter != NULL);
698 FL bool_t
699 charset_iter_next(void)
701 bool_t rv;
702 NYD_ENTER;
704 _CS_ITER_STEP();
705 rv = (_cs_iter != NULL);
706 NYD_LEAVE;
707 return rv;
710 FL bool_t
711 charset_iter_is_valid(void)
713 bool_t rv;
714 NYD_ENTER;
716 rv = (_cs_iter != NULL);
717 NYD_LEAVE;
718 return rv;
721 FL char const *
722 charset_iter(void)
724 char const *rv;
725 NYD_ENTER;
727 rv = _cs_iter;
728 NYD_LEAVE;
729 return rv;
732 FL char const *
733 charset_iter_or_fallback(void)
735 char const *rv;
736 NYD_ENTER;
738 rv = _CS_ITER_GET();
739 NYD_LEAVE;
740 return rv;
743 FL void
744 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
746 NYD_ENTER;
747 outer_storage[0] = _cs_iter_base;
748 outer_storage[1] = _cs_iter;
749 NYD_LEAVE;
752 FL void
753 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
755 NYD_ENTER;
756 _cs_iter_base = outer_storage[0];
757 _cs_iter = outer_storage[1];
758 NYD_LEAVE;
761 #ifdef HAVE_ICONV
762 FL char const *
763 need_hdrconv(struct header *hp, enum gfield w) /* TODO once only, then iter */
765 char const *ret = NULL;
766 NYD_ENTER;
768 if (w & GIDENT) {
769 if (hp->h_mft != NULL) {
770 if (_name_highbit(hp->h_mft))
771 goto jneeds;
773 if (hp->h_from != NULL) {
774 if (_name_highbit(hp->h_from))
775 goto jneeds;
776 } else if (_has_highbit(myaddrs(NULL)))
777 goto jneeds;
778 if (hp->h_organization) {
779 if (_has_highbit(hp->h_organization))
780 goto jneeds;
781 } else if (_has_highbit(ok_vlook(ORGANIZATION)))
782 goto jneeds;
783 if (hp->h_replyto) {
784 if (_name_highbit(hp->h_replyto))
785 goto jneeds;
786 } else if (_has_highbit(ok_vlook(replyto)))
787 goto jneeds;
788 if (hp->h_sender) {
789 if (_name_highbit(hp->h_sender))
790 goto jneeds;
791 } else if (_has_highbit(ok_vlook(sender)))
792 goto jneeds;
794 if ((w & GTO) && _name_highbit(hp->h_to))
795 goto jneeds;
796 if ((w & GCC) && _name_highbit(hp->h_cc))
797 goto jneeds;
798 if ((w & GBCC) && _name_highbit(hp->h_bcc))
799 goto jneeds;
800 if ((w & GSUBJECT) && _has_highbit(hp->h_subject))
801 jneeds:
802 ret = _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
803 NYD_LEAVE;
804 return ret;
806 #endif /* HAVE_ICONV */
808 FL void
809 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
811 /* TODO mime_fromhdr(): is called with strings that contain newlines;
812 * TODO this is the usual newline problem all around the codebase;
813 * TODO i.e., if we strip it, then the display misses it ;>
814 * TODO this is why it is so messy and why S-nail v14.2 plus additional
815 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
816 * TODO why our display reflects what is contained in the message: the 1:1
817 * TODO relationship of message content and display!
818 * TODO instead a header line should be decoded to what it is (a single
819 * TODO line that is) and it should be objective to the backend wether
820 * TODO it'll be folded to fit onto the display or not, e.g., for search
821 * TODO purposes etc. then the only condition we have to honour in here
822 * TODO is that whitespace in between multiple adjacent MIME encoded words
823 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
824 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
825 struct str cin, cout;
826 char *p, *op, *upper, *cbeg;
827 ui32_t convert, lastenc, lastoutl;
828 #ifdef HAVE_ICONV
829 char const *tcs;
830 iconv_t fhicd = (iconv_t)-1;
831 #endif
832 NYD_ENTER;
834 out->l = 0;
835 if (in->l == 0) {
836 *(out->s = smalloc(1)) = '\0';
837 goto jleave;
839 out->s = NULL;
841 #ifdef HAVE_ICONV
842 tcs = charset_get_lc();
843 #endif
844 p = in->s;
845 upper = p + in->l;
846 lastenc = lastoutl = 0;
848 while (p < upper) {
849 op = p;
850 if (*p == '=' && *(p + 1) == '?') {
851 p += 2;
852 cbeg = p;
853 while (p < upper && *p != '?')
854 ++p; /* strip charset */
855 if (p >= upper)
856 goto jnotmime;
857 ++p;
858 #ifdef HAVE_ICONV
859 { size_t i = PTR2SIZE(p - cbeg);
860 char *ltag, *cs = ac_alloc(i);
862 memcpy(cs, cbeg, --i);
863 cs[i] = '\0';
864 /* RFC 2231 extends the RFC 2047 character set definition in
865 * encoded words by language tags - silently strip those off */
866 if ((ltag = strchr(cs, '*')) != NULL)
867 *ltag = '\0';
869 if (fhicd != (iconv_t)-1)
870 n_iconv_close(fhicd);
871 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
872 ac_free(cs);
874 #endif
875 switch (*p) {
876 case 'B': case 'b':
877 convert = CONV_FROMB64;
878 break;
879 case 'Q': case 'q':
880 convert = CONV_FROMQP;
881 break;
882 default: /* invalid, ignore */
883 goto jnotmime;
885 if (*++p != '?')
886 goto jnotmime;
887 cin.s = ++p;
888 cin.l = 1;
889 for (;;) {
890 if (PTRCMP(p + 1, >=, upper))
891 goto jnotmime;
892 if (*p++ == '?' && *p == '=')
893 break;
894 ++cin.l;
896 ++p;
897 --cin.l;
899 cout.s = NULL;
900 cout.l = 0;
901 if (convert == CONV_FROMB64) {
902 /* XXX Take care for, and strip LF from
903 * XXX [Invalid Base64 encoding ignored] */
904 if (b64_decode(&cout, &cin, NULL) == STOP &&
905 cout.s[cout.l - 1] == '\n')
906 --cout.l;
907 } else
908 qp_decode(&cout, &cin, NULL);
910 out->l = lastenc;
911 #ifdef HAVE_ICONV
912 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
913 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
914 convert = n_iconv_str(fhicd, &cin, &cout, NULL, TRU1);
915 out = n_str_add(out, &cin);
916 if (convert) {/* EINVAL at EOS */
917 n_iconv_reset(fhicd);
918 out = n_str_add_buf(out, "?", 1);
920 free(cin.s);
921 } else
922 #endif
923 out = n_str_add(out, &cout);
924 lastenc = lastoutl = out->l;
925 free(cout.s);
926 } else
927 jnotmime: {
928 bool_t onlyws;
930 p = op;
931 onlyws = (lastenc > 0);
932 for (;;) {
933 if (++op == upper)
934 break;
935 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
936 break;
937 if (onlyws && !blankchar(*op))
938 onlyws = FAL0;
941 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
942 p = op;
943 if (!onlyws || lastoutl != lastenc)
944 lastenc = out->l;
945 lastoutl = out->l;
948 out->s[out->l] = '\0';
950 if (flags & TD_ISPR) {
951 makeprint(out, &cout);
952 free(out->s);
953 *out = cout;
955 if (flags & TD_DELCTRL)
956 out->l = delctrl(out->s, out->l);
957 #ifdef HAVE_ICONV
958 if (fhicd != (iconv_t)-1)
959 n_iconv_close(fhicd);
960 #endif
961 jleave:
962 NYD_LEAVE;
963 return;
966 FL char *
967 mime_fromaddr(char const *name)
969 char const *cp, *lastcp;
970 char *res = NULL;
971 size_t ressz = 1, rescur = 0;
972 NYD_ENTER;
974 if (name == NULL)
975 goto jleave;
976 if (*name == '\0') {
977 res = savestr(name);
978 goto jleave;
981 if ((cp = routeaddr(name)) != NULL && cp > name) {
982 _append_conv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
983 lastcp = cp;
984 } else
985 cp = lastcp = name;
987 for ( ; *cp; ++cp) {
988 switch (*cp) {
989 case '(':
990 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
991 lastcp = ++cp;
992 cp = skip_comment(cp);
993 if (--cp > lastcp)
994 _append_conv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
995 lastcp = cp;
996 break;
997 case '"':
998 while (*cp) {
999 if (*++cp == '"')
1000 break;
1001 if (*cp == '\\' && cp[1] != '\0')
1002 ++cp;
1004 break;
1007 if (cp > lastcp)
1008 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1009 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1010 if (rescur == 0)
1011 res = UNCONST("");
1012 else
1013 res[rescur] = '\0';
1014 { char *x = res;
1015 res = savestr(res);
1016 free(x);
1018 jleave:
1019 NYD_LEAVE;
1020 return res;
1023 FL ssize_t
1024 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1025 enum tdflags dflags)
1027 ssize_t rv;
1028 struct quoteflt *qf;
1029 NYD_ENTER;
1031 quoteflt_reset(qf = quoteflt_dummy(), f);
1032 rv = mime_write(ptr, size, f, convert, dflags, qf, NULL);
1033 quoteflt_flush(qf);
1034 NYD_LEAVE;
1035 return rv;
1038 static sigjmp_buf __mimemw_actjmp; /* TODO someday.. */
1039 static int __mimemw_sig; /* TODO someday.. */
1040 static sighandler_type __mimemw_opipe;
1041 static void
1042 __mimemw_onsig(int sig) /* TODO someday, we won't need it no more */
1044 NYD_X; /* Signal handler */
1045 __mimemw_sig = sig;
1046 siglongjmp(__mimemw_actjmp, 1);
1049 FL ssize_t
1050 mime_write(char const *ptr, size_t size, FILE *f,
1051 enum conversion convert, enum tdflags volatile dflags,
1052 struct quoteflt *qf, struct str * volatile rest)
1054 /* TODO note: after send/MIME layer rewrite we will have a string pool
1055 * TODO so that memory allocation count drops down massively; for now,
1056 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1057 struct str in, out;
1058 ssize_t sz;
1059 int state;
1060 NYD_ENTER;
1062 in.s = UNCONST(ptr);
1063 in.l = size;
1064 out.s = NULL;
1065 out.l = 0;
1067 dflags |= _TD_BUFCOPY;
1068 if ((sz = size) == 0) {
1069 if (rest != NULL && rest->l != 0)
1070 goto jconvert;
1071 goto jleave;
1074 #ifdef HAVE_ICONV
1075 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1076 (convert == CONV_TOQP || convert == CONV_8BIT ||
1077 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1078 if (n_iconv_str(iconvd, &out, &in, NULL, FAL0) != 0) {
1079 /* XXX report conversion error? */;
1080 n_iconv_reset(iconvd);
1081 sz = -1;
1082 goto jleave;
1084 in = out;
1085 out.s = NULL;
1086 dflags &= ~_TD_BUFCOPY;
1088 #endif
1090 jconvert:
1091 __mimemw_sig = 0;
1092 __mimemw_opipe = safe_signal(SIGPIPE, &__mimemw_onsig);
1093 if (sigsetjmp(__mimemw_actjmp, 1))
1094 goto jleave;
1096 switch (convert) {
1097 case CONV_FROMQP:
1098 state = qp_decode(&out, &in, rest);
1099 goto jqpb64_dec;
1100 case CONV_TOQP:
1101 qp_encode(&out, &in, QP_NONE);
1102 goto jqpb64_enc;
1103 case CONV_8BIT:
1104 sz = quoteflt_push(qf, in.s, in.l);
1105 break;
1106 case CONV_FROMB64:
1107 rest = NULL;
1108 /* FALLTHRU */
1109 case CONV_FROMB64_T:
1110 state = b64_decode(&out, &in, rest);
1111 jqpb64_dec:
1112 if ((sz = out.l) != 0) {
1113 ui32_t opl = qf->qf_pfix_len;
1114 if (state != OKAY)
1115 qf->qf_pfix_len = 0;
1116 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), rest, qf);
1117 qf->qf_pfix_len = opl;
1119 if (state != OKAY)
1120 sz = -1;
1121 break;
1122 case CONV_TOB64:
1123 b64_encode(&out, &in, B64_LF | B64_MULTILINE);
1124 jqpb64_enc:
1125 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1126 if (sz != (ssize_t)out.l)
1127 sz = -1;
1128 break;
1129 case CONV_FROMHDR:
1130 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1131 sz = quoteflt_push(qf, out.s, out.l);
1132 break;
1133 case CONV_TOHDR:
1134 sz = mime_write_tohdr(&in, f);
1135 break;
1136 case CONV_TOHDR_A:
1137 sz = mime_write_tohdr_a(&in, f);
1138 break;
1139 default:
1140 sz = _fwrite_td(&in, dflags, NULL, qf);
1141 break;
1143 jleave:
1144 if (out.s != NULL)
1145 free(out.s);
1146 if (in.s != ptr)
1147 free(in.s);
1148 safe_signal(SIGPIPE, __mimemw_opipe);
1149 if (__mimemw_sig != 0)
1150 n_raise(__mimemw_sig);
1151 NYD_LEAVE;
1152 return sz;
1155 /* s-it-mode */