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