Do some error checking in puthead()
[s-mailx.git] / mime.c
blobb3a2a1ed9a031b06e24c410a8244f0f671248b38
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") || mime_enc_target() == MIMEE_B64)
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(n_empty);
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(n_empty);
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(n_empty);
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 else{
629 char *cp;
631 cp = savestr(t);
632 for(t = cp; *cp != '\0'; ++cp)
633 *cp = lowerconv(*cp);
635 NYD_LEAVE;
636 return t;
639 #ifdef HAVE_ICONV
640 FL char const *
641 charset_get_8bit(void)
643 char const *t;
644 NYD_ENTER;
646 if ((t = ok_vlook(CHARSET_8BIT_OKEY)) == NULL)
647 t = CHARSET_8BIT;
648 else{
649 char *cp;
651 cp = savestr(t);
652 for(t = cp; *cp != '\0'; ++cp)
653 *cp = lowerconv(*cp);
655 NYD_LEAVE;
656 return t;
658 #endif
660 FL char const *
661 charset_get_lc(void)
663 char const *t;
664 NYD_ENTER;
666 if ((t = ok_vlook(ttycharset)) == NULL)
667 t = CHARSET_8BIT;
668 else{
669 char *cp;
671 cp = savestr(t);
672 for(t = cp; *cp != '\0'; ++cp)
673 *cp = lowerconv(*cp);
675 NYD_LEAVE;
676 return t;
679 FL bool_t
680 charset_iter_reset(char const *a_charset_to_try_first)
682 char const *sarr[3];
683 size_t sarrl[3], len;
684 char *cp;
685 NYD_ENTER;
686 n_UNUSED(a_charset_to_try_first);
688 #ifdef HAVE_ICONV
689 sarr[0] = a_charset_to_try_first;
690 if ((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
691 ok_blook(sendcharsets_else_ttycharset))
692 sarr[1] = charset_get_lc();
693 sarr[2] = charset_get_8bit();
694 #else
695 sarr[2] = charset_get_lc();
696 #endif
698 sarrl[2] = len = strlen(sarr[2]);
699 #ifdef HAVE_ICONV
700 if ((cp = n_UNCONST(sarr[1])) != NULL)
701 len += (sarrl[1] = strlen(cp));
702 else
703 sarrl[1] = 0;
704 if ((cp = n_UNCONST(sarr[0])) != NULL)
705 len += (sarrl[0] = strlen(cp));
706 else
707 sarrl[0] = 0;
708 #endif
710 _cs_iter_base = cp = salloc(len + 1 + 1 +1);
712 #ifdef HAVE_ICONV
713 if ((len = sarrl[0]) != 0) {
714 memcpy(cp, sarr[0], len);
715 cp[len] = ',';
716 cp += ++len;
718 if ((len = sarrl[1]) != 0) {
719 memcpy(cp, sarr[1], len);
720 cp[len] = ',';
721 cp += ++len;
723 #endif
724 len = sarrl[2];
725 memcpy(cp, sarr[2], len);
726 cp[len] = '\0';
728 _CS_ITER_STEP();
729 NYD_LEAVE;
730 return (_cs_iter != NULL);
733 FL bool_t
734 charset_iter_next(void)
736 bool_t rv;
737 NYD_ENTER;
739 _CS_ITER_STEP();
740 rv = (_cs_iter != NULL);
741 NYD_LEAVE;
742 return rv;
745 FL bool_t
746 charset_iter_is_valid(void)
748 bool_t rv;
749 NYD_ENTER;
751 rv = (_cs_iter != NULL);
752 NYD_LEAVE;
753 return rv;
756 FL char const *
757 charset_iter(void)
759 char const *rv;
760 NYD_ENTER;
762 rv = _cs_iter;
763 NYD_LEAVE;
764 return rv;
767 FL char const *
768 charset_iter_or_fallback(void)
770 char const *rv;
771 NYD_ENTER;
773 rv = _CS_ITER_GET();
774 NYD_LEAVE;
775 return rv;
778 FL void
779 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
781 NYD_ENTER;
782 outer_storage[0] = _cs_iter_base;
783 outer_storage[1] = _cs_iter;
784 NYD_LEAVE;
787 FL void
788 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
790 NYD_ENTER;
791 _cs_iter_base = outer_storage[0];
792 _cs_iter = outer_storage[1];
793 NYD_LEAVE;
796 #ifdef HAVE_ICONV
797 FL char const *
798 need_hdrconv(struct header *hp) /* TODO once only, then iter */
800 struct n_header_field *hfp;
801 char const *rv;
802 NYD_ENTER;
804 rv = NULL;
806 if((hfp = hp->h_user_headers) != NULL)
807 do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
808 goto jneeds;
809 while((hfp = hfp->hf_next) != NULL);
811 if((hfp = hp->h_custom_headers) != NULL ||
812 (hp->h_custom_headers = hfp = n_customhdr_query()) != NULL)
813 do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
814 goto jneeds;
815 while((hfp = hfp->hf_next) != NULL);
817 if (hp->h_mft != NULL) {
818 if (_name_highbit(hp->h_mft))
819 goto jneeds;
821 if (hp->h_from != NULL) {
822 if (_name_highbit(hp->h_from))
823 goto jneeds;
824 } else if (_has_highbit(myaddrs(NULL)))
825 goto jneeds;
826 if (hp->h_replyto) {
827 if (_name_highbit(hp->h_replyto))
828 goto jneeds;
829 } else if (_has_highbit(ok_vlook(replyto)))
830 goto jneeds;
831 if (hp->h_sender) {
832 if (_name_highbit(hp->h_sender))
833 goto jneeds;
834 } else if (_has_highbit(ok_vlook(sender)))
835 goto jneeds;
837 if (_name_highbit(hp->h_to))
838 goto jneeds;
839 if (_name_highbit(hp->h_cc))
840 goto jneeds;
841 if (_name_highbit(hp->h_bcc))
842 goto jneeds;
843 if (_has_highbit(hp->h_subject))
844 jneeds:
845 rv = _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
846 NYD_LEAVE;
847 return rv;
849 #endif /* HAVE_ICONV */
851 FL void
852 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
854 /* TODO mime_fromhdr(): is called with strings that contain newlines;
855 * TODO this is the usual newline problem all around the codebase;
856 * TODO i.e., if we strip it, then the display misses it ;>
857 * TODO this is why it is so messy and why S-nail v14.2 plus additional
858 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
859 * TODO why our display reflects what is contained in the message: the 1:1
860 * TODO relationship of message content and display!
861 * TODO instead a header line should be decoded to what it is (a single
862 * TODO line that is) and it should be objective to the backend whether
863 * TODO it'll be folded to fit onto the display or not, e.g., for search
864 * TODO purposes etc. then the only condition we have to honour in here
865 * TODO is that whitespace in between multiple adjacent MIME encoded words
866 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
867 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
868 struct str cin, cout;
869 char *p, *op, *upper;
870 ui32_t convert, lastenc, lastoutl;
871 #ifdef HAVE_ICONV
872 char const *tcs;
873 char *cbeg;
874 iconv_t fhicd = (iconv_t)-1;
875 #endif
876 NYD_ENTER;
878 out->l = 0;
879 if (in->l == 0) {
880 *(out->s = smalloc(1)) = '\0';
881 goto jleave;
883 out->s = NULL;
885 #ifdef HAVE_ICONV
886 tcs = charset_get_lc();
887 #endif
888 p = in->s;
889 upper = p + in->l;
890 lastenc = lastoutl = 0;
892 while (p < upper) {
893 op = p;
894 if (*p == '=' && *(p + 1) == '?') {
895 p += 2;
896 #ifdef HAVE_ICONV
897 cbeg = p;
898 #endif
899 while (p < upper && *p != '?')
900 ++p; /* strip charset */
901 if (p >= upper)
902 goto jnotmime;
903 ++p;
904 #ifdef HAVE_ICONV
905 if (flags & TD_ICONV) {
906 size_t i = PTR2SIZE(p - cbeg);
907 char *ltag, *cs = ac_alloc(i);
909 memcpy(cs, cbeg, --i);
910 cs[i] = '\0';
911 /* RFC 2231 extends the RFC 2047 character set definition in
912 * encoded words by language tags - silently strip those off */
913 if ((ltag = strchr(cs, '*')) != NULL)
914 *ltag = '\0';
916 if (fhicd != (iconv_t)-1)
917 n_iconv_close(fhicd);
918 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
919 ac_free(cs);
921 #endif
922 switch (*p) {
923 case 'B': case 'b':
924 convert = CONV_FROMB64;
925 break;
926 case 'Q': case 'q':
927 convert = CONV_FROMQP;
928 break;
929 default: /* invalid, ignore */
930 goto jnotmime;
932 if (*++p != '?')
933 goto jnotmime;
934 cin.s = ++p;
935 cin.l = 1;
936 for (;;) {
937 if (PTRCMP(p + 1, >=, upper))
938 goto jnotmime;
939 if (*p++ == '?' && *p == '=')
940 break;
941 ++cin.l;
943 ++p;
944 --cin.l;
946 cout.s = NULL;
947 cout.l = 0;
948 if (convert == CONV_FROMB64) {
949 if(!b64_decode_header(&cout, &cin))
950 n_str_assign_cp(&cout, _("[Invalid Base64 encoding]"));
951 }else if(!qp_decode_header(&cout, &cin))
952 n_str_assign_cp(&cout, _("[Invalid Quoted-Printable encoding]"));
954 out->l = lastenc;
955 #ifdef HAVE_ICONV
956 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
957 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
958 convert = n_iconv_str(fhicd, n_ICONV_UNIDEFAULT, &cin, &cout, NULL);
959 out = n_str_add(out, &cin);
960 if (convert) {/* EINVAL at EOS */
961 n_iconv_reset(fhicd);
962 out = n_str_add_buf(out, "?", 1); /* TODO unicode replacement */
964 free(cin.s);
965 } else
966 #endif
967 out = n_str_add(out, &cout);
968 lastenc = lastoutl = out->l;
969 free(cout.s);
970 } else
971 jnotmime: {
972 bool_t onlyws;
974 p = op;
975 onlyws = (lastenc > 0);
976 for (;;) {
977 if (++op == upper)
978 break;
979 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
980 break;
981 if (onlyws && !blankchar(*op))
982 onlyws = FAL0;
985 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
986 p = op;
987 if (!onlyws || lastoutl != lastenc)
988 lastenc = out->l;
989 lastoutl = out->l;
992 out->s[out->l] = '\0';
994 if (flags & TD_ISPR) {
995 makeprint(out, &cout);
996 free(out->s);
997 *out = cout;
999 if (flags & TD_DELCTRL)
1000 out->l = delctrl(out->s, out->l);
1001 #ifdef HAVE_ICONV
1002 if (fhicd != (iconv_t)-1)
1003 n_iconv_close(fhicd);
1004 #endif
1005 jleave:
1006 NYD_LEAVE;
1007 return;
1010 FL char *
1011 mime_fromaddr(char const *name)
1013 char const *cp, *lastcp;
1014 char *res = NULL;
1015 size_t ressz = 1, rescur = 0;
1016 NYD_ENTER;
1018 if (name == NULL)
1019 goto jleave;
1020 if (*name == '\0') {
1021 res = savestr(name);
1022 goto jleave;
1025 if ((cp = routeaddr(name)) != NULL && cp > name) {
1026 _append_conv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
1027 lastcp = cp;
1028 } else
1029 cp = lastcp = name;
1031 for ( ; *cp; ++cp) {
1032 switch (*cp) {
1033 case '(':
1034 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
1035 lastcp = ++cp;
1036 cp = skip_comment(cp);
1037 if (--cp > lastcp)
1038 _append_conv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1039 lastcp = cp;
1040 break;
1041 case '"':
1042 while (*cp) {
1043 if (*++cp == '"')
1044 break;
1045 if (*cp == '\\' && cp[1] != '\0')
1046 ++cp;
1048 break;
1051 if (cp > lastcp)
1052 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1053 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1054 if (rescur == 0)
1055 res = n_UNCONST(n_empty);
1056 else
1057 res[rescur] = '\0';
1058 { char *x = res;
1059 res = savestr(res);
1060 free(x);
1062 jleave:
1063 NYD_LEAVE;
1064 return res;
1067 FL ssize_t
1068 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1069 enum tdflags dflags)
1071 ssize_t rv;
1072 struct quoteflt *qf;
1073 NYD_ENTER;
1075 quoteflt_reset(qf = quoteflt_dummy(), f);
1076 rv = mime_write(ptr, size, f, convert, dflags, qf, NULL, NULL);
1077 quoteflt_flush(qf);
1078 NYD_LEAVE;
1079 return rv;
1082 static sigjmp_buf __mimemw_actjmp; /* TODO someday.. */
1083 static int __mimemw_sig; /* TODO someday.. */
1084 static sighandler_type __mimemw_opipe;
1085 static void
1086 __mimemw_onsig(int sig) /* TODO someday, we won't need it no more */
1088 NYD_X; /* Signal handler */
1089 __mimemw_sig = sig;
1090 siglongjmp(__mimemw_actjmp, 1);
1093 FL ssize_t
1094 mime_write(char const *ptr, size_t size, FILE *f,
1095 enum conversion convert, enum tdflags volatile dflags,
1096 struct quoteflt *qf, struct str * volatile outrest,
1097 struct str * volatile inrest)
1099 /* TODO note: after send/MIME layer rewrite we will have a string pool
1100 * TODO so that memory allocation count drops down massively; for now,
1101 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1102 struct str in, out;
1103 ssize_t volatile sz;
1104 NYD_ENTER;
1106 dflags |= _TD_BUFCOPY;
1108 in.s = n_UNCONST(ptr);
1109 in.l = size;
1110 if(inrest != NULL && inrest->l > 0){
1111 out.s = smalloc(inrest->l + size + 1);
1112 memcpy(out.s, inrest->s, inrest->l);
1113 if(size > 0)
1114 memcpy(&out.s[inrest->l], in.s, size);
1115 size += inrest->l;
1116 inrest->l = 0;
1117 (in.s = out.s)[in.l = size] = '\0';
1118 dflags &= ~_TD_BUFCOPY;
1121 out.s = NULL;
1122 out.l = 0;
1124 if ((sz = size) == 0) {
1125 if (outrest != NULL && outrest->l != 0)
1126 goto jconvert;
1127 goto jleave;
1130 #ifdef HAVE_ICONV
1131 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1132 (convert == CONV_TOQP || convert == CONV_8BIT ||
1133 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1134 if (n_iconv_str(iconvd, n_ICONV_IGN_NOREVERSE, &out, &in, NULL) != 0) {
1135 n_iconv_reset(iconvd);
1136 /* TODO This causes hard-failure. We would need to have an action
1137 * TODO policy FAIL|IGNORE|SETERROR(but continue). Better huh? */
1138 sz = -1;
1139 goto jleave;
1141 in = out;
1142 out.s = NULL;
1143 dflags &= ~_TD_BUFCOPY;
1145 #endif
1147 jconvert:
1148 __mimemw_sig = 0;
1149 __mimemw_opipe = safe_signal(SIGPIPE, &__mimemw_onsig);
1150 if (sigsetjmp(__mimemw_actjmp, 1))
1151 goto jleave;
1153 switch (convert) {
1154 case CONV_FROMQP:
1155 if(!qp_decode_text(&out, &in, outrest, inrest)){
1156 n_err(_("Invalid Quoted-Printable encoding ignored\n"));
1157 sz = 0; /* TODO sz = -1 stops outer levels! */
1158 break;
1160 goto jqpb64_dec;
1161 case CONV_TOQP:
1162 if(qp_encode(&out, &in, QP_NONE) == NULL){
1163 sz = 0; /* TODO sz = -1 stops outer levels! */
1164 break;
1166 goto jqpb64_enc;
1167 case CONV_8BIT:
1168 sz = quoteflt_push(qf, in.s, in.l);
1169 break;
1170 case CONV_FROMB64:
1171 if(!b64_decode_text(&out, &in, outrest, inrest))
1172 goto jeb64;
1173 outrest = NULL;
1174 if(0){
1175 /* FALLTHRU */
1176 case CONV_FROMB64_T:
1177 if(!b64_decode_text(&out, &in, outrest, inrest)){
1178 jeb64:
1179 n_err(_("Invalid Base64 encoding ignored\n"));
1180 sz = 0; /* TODO sz = -1 stops outer levels! */
1181 break;
1184 jqpb64_dec:
1185 if ((sz = out.l) != 0) {
1186 ui32_t opl = qf->qf_pfix_len;
1187 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), outrest, qf);
1188 qf->qf_pfix_len = opl;
1190 break;
1191 case CONV_TOB64:
1192 if(b64_encode(&out, &in, B64_LF | B64_MULTILINE) == NULL){
1193 sz = -1;
1194 break;
1196 jqpb64_enc:
1197 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1198 if (sz != (ssize_t)out.l)
1199 sz = -1;
1200 break;
1201 case CONV_FROMHDR:
1202 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1203 sz = quoteflt_push(qf, out.s, out.l);
1204 break;
1205 case CONV_TOHDR:
1206 sz = mime_write_tohdr(&in, f);
1207 break;
1208 case CONV_TOHDR_A:
1209 sz = mime_write_tohdr_a(&in, f);
1210 break;
1211 default:
1212 sz = _fwrite_td(&in, dflags, NULL, qf);
1213 break;
1215 jleave:
1216 if (out.s != NULL)
1217 free(out.s);
1218 if (in.s != ptr)
1219 free(in.s);
1220 safe_signal(SIGPIPE, __mimemw_opipe);
1221 if (__mimemw_sig != 0)
1222 n_raise(__mimemw_sig);
1223 NYD_LEAVE;
1224 return sz;
1227 /* s-it-mode */