`write'++: !interactive: urlxenc() attachment paths (Ralph Corderoy)..
[s-mailx.git] / mime.c
blob80edaccf95f32ced19fb002d84fbbc1bfc557ec0
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 *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, n_ICONV_DEFAULT, &out, &in, &in) != 0 &&
168 rest != 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 /* TODO 0xFFFD out.s[out.l++] = '[';*/
174 out.s[out.l++] = '?'; /* TODO unicode replacement 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 * whether 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 whether the range has to become encoded or not */
318 i = PTR2SIZE(wend - wcur);
319 j = mime_enc_mustquote(wcur, i, MIMEEF_ISHEAD);
320 /* If it just cannot fit on a SHOULD line length, force encode */
321 if (i >= _MAXCOL) {
322 flags |= _SHOULD_BEE; /* (Sigh: SHOULD only, not MUST..) */
323 goto j_beejump;
325 if ((flags & _SHOULD_BEE) || j > 0) {
326 j_beejump:
327 flags |= _ENCODE;
328 /* Use base64 if requested or more than 50% -37.5-% of the bytes of
329 * the string need to be encoded */
330 if ((flags & _NO_QP) || j >= i >> 1)/*(i >> 2) + (i >> 3))*/
331 flags |= _ENC_B64;
333 DBG( if (flags & _8BIT) assert(flags & _ENCODE); )
335 if (!(flags & _ENCODE)) {
336 /* Encoded word produced, but no linear whitespace for necessary RFC
337 * 2047 separation? Generate artificial data (bad standard!) */
338 if ((flags & (_ENC_LAST | _SPACE)) == _ENC_LAST) {
339 if (col >= _MAXCOL) {
340 putc('\n', fo);
341 ++sz;
342 col = 0;
344 putc(' ', fo);
345 ++sz;
346 ++col;
349 jnoenc_putws:
350 flags &= ~_ENC_LAST;
352 /* todo No effort here: (1) v15.0 has to bring complete rewrite,
353 * todo (2) the standard is braindead and (3) usually this is one
354 * todo word only, and why be smarter than the standard? */
355 jnoenc_retry:
356 i = PTR2SIZE(wend - wbot);
357 if (i + col <= (flags & _OVERLONG ? MIME_LINELEN_MAX : _MAXCOL)) {
358 i = fwrite(wbot, sizeof *wbot, i, fo);
359 sz += i;
360 col += i;
361 continue;
364 /* Doesn't fit, try to break the line first; */
365 if (col > 1) {
366 putc('\n', fo);
367 if (whitechar(*wbot)) {
368 putc((uc_i)*wbot, fo);
369 ++wbot;
370 } else
371 putc(' ', fo); /* Bad standard: artificial data! */
372 sz += 2;
373 col = 1;
374 flags |= _OVERLONG;
375 goto jnoenc_retry;
378 /* It is so long that it needs to be broken, effectively causing
379 * artificial spaces to be inserted (bad standard), yuck */
380 /* todo This is not multibyte safe, as above; and completely stupid
381 * todo P.S.: our _SHOULD_BEE prevents these cases in the meanwhile */
382 /* FIXME OPT_UNICODE and parse using UTF-8 sync possibility! */
383 wcur = wbot + MIME_LINELEN_MAX - 8;
384 while (wend > wcur)
385 wend -= 4;
386 goto jnoenc_retry;
387 } else {
388 /* Encoding to encoded word(s); deal with leading whitespace, place
389 * a separator first as necessary: encoded words must always be
390 * separated from text and other encoded words with linear WS.
391 * And if an encoded word was last, intermediate whitespace must
392 * also be encoded, otherwise it would get stripped away! */
393 wcur = UNCONST("");
394 if ((flags & (_ENC_LAST | _SPACE)) != _SPACE) {
395 /* Reinclude whitespace */
396 flags &= ~_SPACE;
397 /* We don't need to place a separator at the very beginning */
398 if (!(flags & _FIRST))
399 wcur = UNCONST(" ");
400 } else
401 wcur = wbot++;
403 flags |= _ENC_LAST;
404 pstate |= PS_HEADER_NEEDED_MIME;
406 /* RFC 2047:
407 * An 'encoded-word' may not be more than 75 characters long,
408 * including 'charset', 'encoding', 'encoded-text', and
409 * delimiters. If it is desirable to encode more text than will
410 * fit in an 'encoded-word' of 75 characters, multiple
411 * 'encoded-word's (separated by CRLF SPACE) may be used.
413 * While there is no limit to the length of a multiple-line
414 * header field, each line of a header field that contains one
415 * or more 'encoded-word's is limited to 76 characters */
416 jenc_retry:
417 cin.s = UNCONST(wbot);
418 cin.l = PTR2SIZE(wend - wbot);
420 if (flags & _ENC_B64)
421 j = b64_encode(&cout, &cin, B64_ISHEAD | B64_ISENCWORD)->l;
422 else
423 j = qp_encode(&cout, &cin, QP_ISHEAD | QP_ISENCWORD)->l;
424 /* (Avoid trigraphs in the RFC 2047 placeholder..) */
425 i = j + (flags & _8BIT ? cset8_len : cset7_len) + sizeof("=!!B!!=") -1;
426 if (*wcur != '\0')
427 ++i;
429 jenc_retry_same:
430 /* Unfortunately RFC 2047 explicitly disallows encoded words to be
431 * longer (just like RFC 5322's "a line SHOULD fit in 78 but MAY be
432 * 998 characters long"), so we cannot use the _OVERLONG mechanism,
433 * even though all tested mailers seem to support it */
434 if (i + col <= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ _MAXCOL)) {
435 fprintf(fo, "%.1s=?%s?%c?%.*s?=",
436 wcur, (flags & _8BIT ? cset8 : cset7),
437 (flags & _ENC_B64 ? 'B' : 'Q'),
438 (int)cout.l, cout.s);
439 sz += i;
440 col += i;
441 continue;
444 /* Doesn't fit, try to break the line first */
445 /* TODO I've commented out the _FIRST test since we (1) cannot do
446 * TODO _OVERLONG since (MUAs support but) the standard disallows,
447 * TODO and because of our iconv problem i prefer an empty first line
448 * TODO in favour of a possibly messed up multibytes character. :-( */
449 if (col > 1 /* TODO && !(flags & _FIRST)*/) {
450 putc('\n', fo);
451 sz += 2;
452 col = 1;
453 if (!(flags & _SPACE)) {
454 putc(' ', fo);
455 wcur = UNCONST("");
456 /*flags |= _OVERLONG;*/
457 goto jenc_retry_same;
458 } else {
459 putc((uc_i)*wcur, fo);
460 if (whitechar(*(wcur = wbot)))
461 ++wbot;
462 else {
463 flags &= ~_SPACE;
464 wcur = UNCONST("");
466 /*flags &= ~_OVERLONG;*/
467 goto jenc_retry;
471 /* It is so long that it needs to be broken, effectively causing
472 * artificial data to be inserted (bad standard), yuck */
473 /* todo This is not multibyte safe, as above */
474 /*if (!(flags & _OVERLONG)) { Mechanism explicitly forbidden by 2047
475 flags |= _OVERLONG;
476 goto jenc_retry;
479 /* FIXME OPT_UNICODE and parse using UTF-8 sync possibility! */
480 i = PTR2SIZE(wend - wbot) + !!(flags & _SPACE);
481 j = 3 + !(flags & _ENC_B64);
482 for (;;) {
483 wend -= j;
484 i -= j;
485 /* (Note the problem most likely is the transfer-encoding blow,
486 * which is why we test this *after* the decrements.. */
487 if (i <= _MAXCOL)
488 break;
490 goto jenc_retry;
494 if (cout.s != NULL)
495 free(cout.s);
496 NYD_LEAVE;
497 return sz;
500 static ssize_t
501 convhdra(char const *str, size_t len, FILE *fp)
503 #ifdef HAVE_ICONV
504 struct str ciconv;
505 #endif
506 struct str cin;
507 ssize_t ret = 0;
508 NYD_ENTER;
510 cin.s = UNCONST(str);
511 cin.l = len;
512 #ifdef HAVE_ICONV
513 ciconv.s = NULL;
514 if (iconvd != (iconv_t)-1) {
515 ciconv.l = 0;
516 if(n_iconv_str(iconvd, n_ICONV_IGN_NOREVERSE, &ciconv, &cin, NULL) != 0){
517 n_iconv_reset(iconvd);
518 goto jleave;
520 cin = ciconv;
522 #endif
523 ret = mime_write_tohdr(&cin, fp);
524 #ifdef HAVE_ICONV
525 jleave:
526 if (ciconv.s != NULL)
527 free(ciconv.s);
528 #endif
529 NYD_LEAVE;
530 return ret;
533 static ssize_t
534 mime_write_tohdr_a(struct str *in, FILE *f) /* TODO error handling */
536 char const *cp, *lastcp;
537 ssize_t sz, x;
538 NYD_ENTER;
540 in->s[in->l] = '\0';
541 lastcp = in->s;
542 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
543 if ((sz = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0)
544 goto jleave;
545 lastcp = cp;
546 } else {
547 cp = in->s;
548 sz = 0;
551 for ( ; *cp != '\0'; ++cp) {
552 switch (*cp) {
553 case '(':
554 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp + 1), f);
555 lastcp = ++cp;
556 cp = skip_comment(cp);
557 if (--cp > lastcp) {
558 if ((x = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0) {
559 sz = x;
560 goto jleave;
562 sz += x;
564 lastcp = cp;
565 break;
566 case '"':
567 while (*cp) {
568 if (*++cp == '"')
569 break;
570 if (*cp == '\\' && cp[1] != '\0')
571 ++cp;
573 break;
576 if (cp > lastcp)
577 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp), f);
578 jleave:
579 NYD_LEAVE;
580 return sz;
583 static void
584 _append_str(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
586 NYD_ENTER;
587 *buf = srealloc(*buf, *sz += len);
588 memcpy(&(*buf)[*pos], str, len);
589 *pos += len;
590 NYD_LEAVE;
593 static void
594 _append_conv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
596 struct str in, out;
597 NYD_ENTER;
599 in.s = UNCONST(str);
600 in.l = len;
601 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
602 _append_str(buf, sz, pos, out.s, out.l);
603 free(out.s);
604 NYD_LEAVE;
607 FL char const *
608 charset_get_7bit(void)
610 char const *t;
611 NYD_ENTER;
613 if ((t = ok_vlook(charset_7bit)) == NULL)
614 t = CHARSET_7BIT;
615 NYD_LEAVE;
616 return t;
619 #ifdef HAVE_ICONV
620 FL char const *
621 charset_get_8bit(void)
623 char const *t;
624 NYD_ENTER;
626 if ((t = ok_vlook(CHARSET_8BIT_OKEY)) == NULL)
627 t = CHARSET_8BIT;
628 NYD_LEAVE;
629 return t;
631 #endif
633 FL char const *
634 charset_get_lc(void)
636 char const *t;
637 NYD_ENTER;
639 if ((t = ok_vlook(ttycharset)) == NULL)
640 t = CHARSET_8BIT;
641 NYD_LEAVE;
642 return t;
645 FL bool_t
646 charset_iter_reset(char const *a_charset_to_try_first)
648 char const *sarr[3];
649 size_t sarrl[3], len;
650 char *cp;
651 NYD_ENTER;
652 UNUSED(a_charset_to_try_first);
654 #ifdef HAVE_ICONV
655 sarr[0] = a_charset_to_try_first;
656 if ((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
657 ok_blook(sendcharsets_else_ttycharset))
658 sarr[1] = charset_get_lc();
659 sarr[2] = charset_get_8bit();
660 #else
661 sarr[2] = charset_get_lc();
662 #endif
664 sarrl[2] = len = strlen(sarr[2]);
665 #ifdef HAVE_ICONV
666 if ((cp = UNCONST(sarr[1])) != NULL)
667 len += (sarrl[1] = strlen(cp));
668 else
669 sarrl[1] = 0;
670 if ((cp = UNCONST(sarr[0])) != NULL)
671 len += (sarrl[0] = strlen(cp));
672 else
673 sarrl[0] = 0;
674 #endif
676 _cs_iter_base = cp = salloc(len + 1 + 1 +1);
678 #ifdef HAVE_ICONV
679 if ((len = sarrl[0]) != 0) {
680 memcpy(cp, sarr[0], len);
681 cp[len] = ',';
682 cp += ++len;
684 if ((len = sarrl[1]) != 0) {
685 memcpy(cp, sarr[1], len);
686 cp[len] = ',';
687 cp += ++len;
689 #endif
690 len = sarrl[2];
691 memcpy(cp, sarr[2], len);
692 cp[len] = '\0';
694 _CS_ITER_STEP();
695 NYD_LEAVE;
696 return (_cs_iter != NULL);
699 FL bool_t
700 charset_iter_next(void)
702 bool_t rv;
703 NYD_ENTER;
705 _CS_ITER_STEP();
706 rv = (_cs_iter != NULL);
707 NYD_LEAVE;
708 return rv;
711 FL bool_t
712 charset_iter_is_valid(void)
714 bool_t rv;
715 NYD_ENTER;
717 rv = (_cs_iter != NULL);
718 NYD_LEAVE;
719 return rv;
722 FL char const *
723 charset_iter(void)
725 char const *rv;
726 NYD_ENTER;
728 rv = _cs_iter;
729 NYD_LEAVE;
730 return rv;
733 FL char const *
734 charset_iter_or_fallback(void)
736 char const *rv;
737 NYD_ENTER;
739 rv = _CS_ITER_GET();
740 NYD_LEAVE;
741 return rv;
744 FL void
745 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
747 NYD_ENTER;
748 outer_storage[0] = _cs_iter_base;
749 outer_storage[1] = _cs_iter;
750 NYD_LEAVE;
753 FL void
754 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
756 NYD_ENTER;
757 _cs_iter_base = outer_storage[0];
758 _cs_iter = outer_storage[1];
759 NYD_LEAVE;
762 #ifdef HAVE_ICONV
763 FL char const *
764 need_hdrconv(struct header *hp) /* TODO once only, then iter */
766 struct n_header_field *hfp;
767 char const *rv;
768 NYD_ENTER;
770 rv = NULL;
772 if((hfp = hp->h_user_headers) != NULL)
773 do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
774 goto jneeds;
775 while((hfp = hfp->hf_next) != NULL);
777 if((hfp = hp->h_custom_headers) != NULL ||
778 (hp->h_custom_headers = hfp = n_customhdr_query()) != NULL)
779 do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
780 goto jneeds;
781 while((hfp = hfp->hf_next) != NULL);
783 if (hp->h_mft != NULL) {
784 if (_name_highbit(hp->h_mft))
785 goto jneeds;
787 if (hp->h_from != NULL) {
788 if (_name_highbit(hp->h_from))
789 goto jneeds;
790 } else if (_has_highbit(myaddrs(NULL)))
791 goto jneeds;
792 if (hp->h_replyto) {
793 if (_name_highbit(hp->h_replyto))
794 goto jneeds;
795 } else if (_has_highbit(ok_vlook(replyto)))
796 goto jneeds;
797 if (hp->h_sender) {
798 if (_name_highbit(hp->h_sender))
799 goto jneeds;
800 } else if (_has_highbit(ok_vlook(sender)))
801 goto jneeds;
803 if (_name_highbit(hp->h_to))
804 goto jneeds;
805 if (_name_highbit(hp->h_cc))
806 goto jneeds;
807 if (_name_highbit(hp->h_bcc))
808 goto jneeds;
809 if (_has_highbit(hp->h_subject))
810 jneeds:
811 rv = _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
812 NYD_LEAVE;
813 return rv;
815 #endif /* HAVE_ICONV */
817 FL void
818 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
820 /* TODO mime_fromhdr(): is called with strings that contain newlines;
821 * TODO this is the usual newline problem all around the codebase;
822 * TODO i.e., if we strip it, then the display misses it ;>
823 * TODO this is why it is so messy and why S-nail v14.2 plus additional
824 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
825 * TODO why our display reflects what is contained in the message: the 1:1
826 * TODO relationship of message content and display!
827 * TODO instead a header line should be decoded to what it is (a single
828 * TODO line that is) and it should be objective to the backend whether
829 * TODO it'll be folded to fit onto the display or not, e.g., for search
830 * TODO purposes etc. then the only condition we have to honour in here
831 * TODO is that whitespace in between multiple adjacent MIME encoded words
832 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
833 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
834 struct str cin, cout;
835 char *p, *op, *upper;
836 ui32_t convert, lastenc, lastoutl;
837 #ifdef HAVE_ICONV
838 char const *tcs;
839 char *cbeg;
840 iconv_t fhicd = (iconv_t)-1;
841 #endif
842 NYD_ENTER;
844 out->l = 0;
845 if (in->l == 0) {
846 *(out->s = smalloc(1)) = '\0';
847 goto jleave;
849 out->s = NULL;
851 #ifdef HAVE_ICONV
852 tcs = charset_get_lc();
853 #endif
854 p = in->s;
855 upper = p + in->l;
856 lastenc = lastoutl = 0;
858 while (p < upper) {
859 op = p;
860 if (*p == '=' && *(p + 1) == '?') {
861 p += 2;
862 #ifdef HAVE_ICONV
863 cbeg = p;
864 #endif
865 while (p < upper && *p != '?')
866 ++p; /* strip charset */
867 if (p >= upper)
868 goto jnotmime;
869 ++p;
870 #ifdef HAVE_ICONV
871 if (flags & TD_ICONV) {
872 size_t i = PTR2SIZE(p - cbeg);
873 char *ltag, *cs = ac_alloc(i);
875 memcpy(cs, cbeg, --i);
876 cs[i] = '\0';
877 /* RFC 2231 extends the RFC 2047 character set definition in
878 * encoded words by language tags - silently strip those off */
879 if ((ltag = strchr(cs, '*')) != NULL)
880 *ltag = '\0';
882 if (fhicd != (iconv_t)-1)
883 n_iconv_close(fhicd);
884 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
885 ac_free(cs);
887 #endif
888 switch (*p) {
889 case 'B': case 'b':
890 convert = CONV_FROMB64;
891 break;
892 case 'Q': case 'q':
893 convert = CONV_FROMQP;
894 break;
895 default: /* invalid, ignore */
896 goto jnotmime;
898 if (*++p != '?')
899 goto jnotmime;
900 cin.s = ++p;
901 cin.l = 1;
902 for (;;) {
903 if (PTRCMP(p + 1, >=, upper))
904 goto jnotmime;
905 if (*p++ == '?' && *p == '=')
906 break;
907 ++cin.l;
909 ++p;
910 --cin.l;
912 cout.s = NULL;
913 cout.l = 0;
914 if (convert == CONV_FROMB64) {
915 /* XXX Take care for, and strip LF from
916 * XXX [Invalid Base64 encoding ignored] */
917 if (b64_decode(&cout, &cin, NULL) == STOP &&
918 cout.s[cout.l - 1] == '\n')
919 --cout.l;
920 } else
921 qp_decode(&cout, &cin, NULL);
923 out->l = lastenc;
924 #ifdef HAVE_ICONV
925 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
926 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
927 convert = n_iconv_str(fhicd, n_ICONV_DEFAULT, &cin, &cout, NULL);
928 out = n_str_add(out, &cin);
929 if (convert) {/* EINVAL at EOS */
930 n_iconv_reset(fhicd);
931 out = n_str_add_buf(out, "?", 1); /* TODO unicode replacement */
933 free(cin.s);
934 } else
935 #endif
936 out = n_str_add(out, &cout);
937 lastenc = lastoutl = out->l;
938 free(cout.s);
939 } else
940 jnotmime: {
941 bool_t onlyws;
943 p = op;
944 onlyws = (lastenc > 0);
945 for (;;) {
946 if (++op == upper)
947 break;
948 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
949 break;
950 if (onlyws && !blankchar(*op))
951 onlyws = FAL0;
954 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
955 p = op;
956 if (!onlyws || lastoutl != lastenc)
957 lastenc = out->l;
958 lastoutl = out->l;
961 out->s[out->l] = '\0';
963 if (flags & TD_ISPR) {
964 makeprint(out, &cout);
965 free(out->s);
966 *out = cout;
968 if (flags & TD_DELCTRL)
969 out->l = delctrl(out->s, out->l);
970 #ifdef HAVE_ICONV
971 if (fhicd != (iconv_t)-1)
972 n_iconv_close(fhicd);
973 #endif
974 jleave:
975 NYD_LEAVE;
976 return;
979 FL char *
980 mime_fromaddr(char const *name)
982 char const *cp, *lastcp;
983 char *res = NULL;
984 size_t ressz = 1, rescur = 0;
985 NYD_ENTER;
987 if (name == NULL)
988 goto jleave;
989 if (*name == '\0') {
990 res = savestr(name);
991 goto jleave;
994 if ((cp = routeaddr(name)) != NULL && cp > name) {
995 _append_conv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
996 lastcp = cp;
997 } else
998 cp = lastcp = name;
1000 for ( ; *cp; ++cp) {
1001 switch (*cp) {
1002 case '(':
1003 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
1004 lastcp = ++cp;
1005 cp = skip_comment(cp);
1006 if (--cp > lastcp)
1007 _append_conv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1008 lastcp = cp;
1009 break;
1010 case '"':
1011 while (*cp) {
1012 if (*++cp == '"')
1013 break;
1014 if (*cp == '\\' && cp[1] != '\0')
1015 ++cp;
1017 break;
1020 if (cp > lastcp)
1021 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1022 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1023 if (rescur == 0)
1024 res = UNCONST("");
1025 else
1026 res[rescur] = '\0';
1027 { char *x = res;
1028 res = savestr(res);
1029 free(x);
1031 jleave:
1032 NYD_LEAVE;
1033 return res;
1036 FL ssize_t
1037 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1038 enum tdflags dflags)
1040 ssize_t rv;
1041 struct quoteflt *qf;
1042 NYD_ENTER;
1044 quoteflt_reset(qf = quoteflt_dummy(), f);
1045 rv = mime_write(ptr, size, f, convert, dflags, qf, NULL);
1046 quoteflt_flush(qf);
1047 NYD_LEAVE;
1048 return rv;
1051 static sigjmp_buf __mimemw_actjmp; /* TODO someday.. */
1052 static int __mimemw_sig; /* TODO someday.. */
1053 static sighandler_type __mimemw_opipe;
1054 static void
1055 __mimemw_onsig(int sig) /* TODO someday, we won't need it no more */
1057 NYD_X; /* Signal handler */
1058 __mimemw_sig = sig;
1059 siglongjmp(__mimemw_actjmp, 1);
1062 FL ssize_t
1063 mime_write(char const *ptr, size_t size, FILE *f,
1064 enum conversion convert, enum tdflags volatile dflags,
1065 struct quoteflt *qf, struct str * volatile rest)
1067 /* TODO note: after send/MIME layer rewrite we will have a string pool
1068 * TODO so that memory allocation count drops down massively; for now,
1069 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1070 struct str in, out;
1071 ssize_t volatile sz;
1072 int state;
1073 NYD_ENTER;
1075 in.s = UNCONST(ptr);
1076 in.l = size;
1077 out.s = NULL;
1078 out.l = 0;
1080 dflags |= _TD_BUFCOPY;
1081 if ((sz = size) == 0) {
1082 if (rest != NULL && rest->l != 0)
1083 goto jconvert;
1084 goto jleave;
1087 #ifdef HAVE_ICONV
1088 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1089 (convert == CONV_TOQP || convert == CONV_8BIT ||
1090 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1091 if (n_iconv_str(iconvd, n_ICONV_IGN_NOREVERSE, &out, &in, NULL) != 0) {
1092 /* XXX report conversion error? */;
1093 n_iconv_reset(iconvd);
1094 sz = -1;
1095 goto jleave;
1097 in = out;
1098 out.s = NULL;
1099 dflags &= ~_TD_BUFCOPY;
1101 #endif
1103 jconvert:
1104 __mimemw_sig = 0;
1105 __mimemw_opipe = safe_signal(SIGPIPE, &__mimemw_onsig);
1106 if (sigsetjmp(__mimemw_actjmp, 1))
1107 goto jleave;
1109 switch (convert) {
1110 case CONV_FROMQP:
1111 state = qp_decode(&out, &in, rest);
1112 goto jqpb64_dec;
1113 case CONV_TOQP:
1114 qp_encode(&out, &in, QP_NONE);
1115 goto jqpb64_enc;
1116 case CONV_8BIT:
1117 sz = quoteflt_push(qf, in.s, in.l);
1118 break;
1119 case CONV_FROMB64:
1120 rest = NULL;
1121 /* FALLTHRU */
1122 case CONV_FROMB64_T:
1123 state = b64_decode(&out, &in, rest);
1124 jqpb64_dec:
1125 if ((sz = out.l) != 0) {
1126 ui32_t opl = qf->qf_pfix_len;
1127 if (state != OKAY)
1128 qf->qf_pfix_len = 0;
1129 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), rest, qf);
1130 qf->qf_pfix_len = opl;
1132 if (state != OKAY)
1133 sz = -1;
1134 break;
1135 case CONV_TOB64:
1136 b64_encode(&out, &in, B64_LF | B64_MULTILINE);
1137 jqpb64_enc:
1138 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1139 if (sz != (ssize_t)out.l)
1140 sz = -1;
1141 break;
1142 case CONV_FROMHDR:
1143 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1144 sz = quoteflt_push(qf, out.s, out.l);
1145 break;
1146 case CONV_TOHDR:
1147 sz = mime_write_tohdr(&in, f);
1148 break;
1149 case CONV_TOHDR_A:
1150 sz = mime_write_tohdr_a(&in, f);
1151 break;
1152 default:
1153 sz = _fwrite_td(&in, dflags, NULL, qf);
1154 break;
1156 jleave:
1157 if (out.s != NULL)
1158 free(out.s);
1159 if (in.s != ptr)
1160 free(in.s);
1161 safe_signal(SIGPIPE, __mimemw_opipe);
1162 if (__mimemw_sig != 0)
1163 n_raise(__mimemw_sig);
1164 NYD_LEAVE;
1165 return sz;
1168 /* s-it-mode */