Boing.Boom.Tschak. More fixes in line with [0ef07198,ee4de6e4]
[s-mailx.git] / mime.c
blob1f6ea66c69374b9b326263a2a24d918e8ccc8aa7
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ MIME support functions.
3 *@ TODO Complete rewrite.
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 */
8 /*
9 * Copyright (c) 2000
10 * Gunnar Ritter. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. All advertising materials mentioning features or use of this software
21 * must display the following acknowledgement:
22 * This product includes software developed by Gunnar Ritter
23 * and his contributors.
24 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
25 * may be used to endorse or promote products derived from this software
26 * without specific prior written permission.
28 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * SUCH DAMAGE.
40 #undef n_FILE
41 #define n_FILE mime
43 #ifndef HAVE_AMALGAMATION
44 # include "nail.h"
45 #endif
47 /* Don't ask, but it keeps body and soul together */
48 enum a_mime_structure_hack{
49 a_MIME_SH_NONE,
50 a_MIME_SH_COMMENT,
51 a_MIME_SH_QUOTE
54 static char *_cs_iter_base, *_cs_iter;
55 #ifdef HAVE_ICONV
56 # define _CS_ITER_GET() \
57 ((_cs_iter != NULL) ? _cs_iter : ok_vlook(CHARSET_8BIT_OKEY))
58 #else
59 # define _CS_ITER_GET() ((_cs_iter != NULL) ? _cs_iter : ok_vlook(ttycharset))
60 #endif
61 #define _CS_ITER_STEP() _cs_iter = n_strsep(&_cs_iter_base, ',', TRU1)
63 /* Is 7-bit enough? */
64 #ifdef HAVE_ICONV
65 static bool_t _has_highbit(char const *s);
66 static bool_t _name_highbit(struct name *np);
67 #endif
69 /* fwrite(3) while checking for displayability */
70 static ssize_t _fwrite_td(struct str const *input, enum tdflags flags,
71 struct str *outrest, struct quoteflt *qf);
73 /* Convert header fields to RFC 2047 format and write to the file fo */
74 static ssize_t mime_write_tohdr(struct str *in, FILE *fo,
75 size_t *colp, enum a_mime_structure_hack msh);
77 /* Write len characters of the passed string to the passed file, doing charset
78 * and header conversion */
80 /* Write an address to a header field */
81 static ssize_t mime_write_tohdr_a(struct str *in, FILE *f,
82 size_t *colp);
83 #ifdef HAVE_ICONV
84 static ssize_t a_mime__convhdra(struct str *inp, FILE *fp, size_t *colp,
85 enum a_mime_structure_hack msh);
86 #else
87 # define a_mime__convhdra(S,F,C,MSH) mime_write_tohdr(S, F, C, MSH)
88 #endif
90 /* Append to buf, handling resizing */
91 static void _append_str(char **buf, size_t *sz, size_t *pos,
92 char const *str, size_t len);
93 static void _append_conv(char **buf, size_t *sz, size_t *pos,
94 char const *str, size_t len);
96 #ifdef HAVE_ICONV
97 static bool_t
98 _has_highbit(char const *s)
100 bool_t rv = TRU1;
101 NYD_ENTER;
103 if (s) {
105 if ((ui8_t)*s & 0x80)
106 goto jleave;
107 while (*s++ != '\0');
109 rv = FAL0;
110 jleave:
111 NYD_LEAVE;
112 return rv;
115 static bool_t
116 _name_highbit(struct name *np)
118 bool_t rv = TRU1;
119 NYD_ENTER;
121 while (np) {
122 if (_has_highbit(np->n_name) || _has_highbit(np->n_fullname))
123 goto jleave;
124 np = np->n_flink;
126 rv = FAL0;
127 jleave:
128 NYD_LEAVE;
129 return rv;
131 #endif /* HAVE_ICONV */
133 static sigjmp_buf __mimefwtd_actjmp; /* TODO someday.. */
134 static int __mimefwtd_sig; /* TODO someday.. */
135 static sighandler_type __mimefwtd_opipe;
136 static void
137 __mimefwtd_onsig(int sig) /* TODO someday, we won't need it no more */
139 NYD_X; /* Signal handler */
140 __mimefwtd_sig = sig;
141 siglongjmp(__mimefwtd_actjmp, 1);
144 static ssize_t
145 _fwrite_td(struct str const *input, enum tdflags flags, struct str *outrest,
146 struct quoteflt *qf)
148 /* TODO note: after send/MIME layer rewrite we will have a string pool
149 * TODO so that memory allocation count drops down massively; for now,
150 * TODO v14.* that is, we pay a lot & heavily depend on the allocator */
151 /* TODO well if we get a broken pipe here, and it happens to
152 * TODO happen pretty easy when sleeping in a full pipe buffer,
153 * TODO then the current codebase performs longjump away;
154 * TODO this leaves memory leaks behind ('think up to 3 per,
155 * TODO dep. upon alloca availability). For this to be fixed
156 * TODO we either need to get rid of the longjmp()s (tm) or
157 * TODO the storage must come from the outside or be tracked
158 * TODO in a carrier struct. Best both. But storage reuse
159 * TODO would be a bigbig win besides */
160 /* *input* _may_ point to non-modifyable buffer; but even then it only
161 * needs to be dup'ed away if we have to transform the content */
162 struct str in, out;
163 ssize_t rv;
164 NYD_ENTER;
165 n_UNUSED(outrest);
167 in = *input;
168 out.s = NULL;
169 out.l = 0;
171 #ifdef HAVE_ICONV
172 if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
173 char *buf = NULL;
175 if (outrest != NULL && outrest->l > 0) {
176 in.l = outrest->l + input->l;
177 in.s = buf = smalloc(in.l +1);
178 memcpy(in.s, outrest->s, outrest->l);
179 memcpy(&in.s[outrest->l], input->s, input->l);
180 outrest->l = 0;
183 if (n_iconv_str(iconvd, n_ICONV_UNIDEFAULT, &out, &in, &in) != 0 &&
184 outrest != NULL && in.l > 0) {
185 n_iconv_reset(iconvd);
186 /* Incomplete multibyte at EOF is special */
187 if (flags & _TD_EOF) {
188 out.s = srealloc(out.s, out.l + sizeof(n_unirepl));
189 if(options & OPT_UNICODE){
190 memcpy(&out.s[out.l], n_unirepl, sizeof(n_unirepl) -1);
191 out.l += sizeof(n_unirepl) -1;
192 }else
193 out.s[out.l++] = '?';
194 } else
195 n_str_add(outrest, &in);
197 in = out;
198 out.l = 0;
199 out.s = NULL;
200 flags &= ~_TD_BUFCOPY;
202 if (buf != NULL)
203 free(buf);
204 }else
205 #endif
206 /* Else, if we will modify the data bytes and thus introduce the potential
207 * of messing up multibyte sequences which become splitted over buffer
208 * boundaries TODO and unless we don't have our filter chain which will
209 * TODO make these hacks go by, buffer data until we see a NL */
210 if((flags & (TD_ISPR | TD_DELCTRL)) && outrest != NULL &&
211 #ifdef HAVE_ICONV
212 iconvd == (iconv_t)-1 &&
213 #endif
214 (!(flags & _TD_EOF) || outrest->l > 0)
216 size_t i;
217 char *cp;
219 for (cp = &in.s[in.l]; cp > in.s && cp[-1] != '\n'; --cp)
221 i = PTR2SIZE(cp - in.s);
223 if (i != in.l) {
224 if (i > 0) {
225 n_str_assign_buf(outrest, cp, in.l - i);
226 cp = smalloc(i +1);
227 memcpy(cp, in.s, in.l = i);
228 (in.s = cp)[in.l = i] = '\0';
229 flags &= ~_TD_BUFCOPY;
230 } else {
231 n_str_add_buf(outrest, input->s, input->l);
232 rv = 0;
233 goto jleave;
238 if (flags & TD_ISPR)
239 makeprint(&in, &out);
240 else if (flags & _TD_BUFCOPY)
241 n_str_dup(&out, &in);
242 else
243 out = in;
244 if (flags & TD_DELCTRL)
245 out.l = delctrl(out.s, out.l);
247 __mimefwtd_sig = 0;
248 __mimefwtd_opipe = safe_signal(SIGPIPE, &__mimefwtd_onsig);
249 if (sigsetjmp(__mimefwtd_actjmp, 1)) {
250 rv = 0;
251 goto j__sig;
254 rv = quoteflt_push(qf, out.s, out.l);
256 j__sig:
257 if (out.s != in.s)
258 free(out.s);
259 if (in.s != input->s)
260 free(in.s);
261 safe_signal(SIGPIPE, __mimefwtd_opipe);
262 if (__mimefwtd_sig != 0)
263 n_raise(__mimefwtd_sig);
264 jleave:
265 NYD_LEAVE;
266 return rv;
269 static ssize_t
270 mime_write_tohdr(struct str *in, FILE *fo, size_t *colp,
271 enum a_mime_structure_hack msh)
273 /* TODO mime_write_tohdr(): we don't know the name of our header->maxcol..
274 * TODO MIME/send layer rewrite: more available state!!
275 * TODO Because of this we cannot make a difference in between structured
276 * TODO and unstructured headers (RFC 2047, 5. (2))
277 * TODO This means, e.g., that this gets called multiple times for a
278 * TODO structured header and always starts thinking it is at column 0.
279 * TODO I.e., it may get called for only the content of a comment etc.,
280 * TODO not knowing anything of its context.
281 * TODO Instead we should have a list of header body content tokens,
282 * TODO convert them, and then dump the converted tokens, breaking lines.
283 * TODO I.e., get rid of convhdra, mime_write_tohdr_a and such...
284 * TODO Somewhen, the following should produce smooth stuff:
285 * TODO ' "Hallo\"," Dr. Backe "Bl\"ö\"d" (Gell) <ha@llöch.en>
286 * TODO "Nochm\"a\"l"<ta@tu.da>(Dümm)'
287 * TODO NOT MULTIBYTE SAFE IF AN ENCODED WORD HAS TO BE SPLITTED!
288 * TODO To be better we had to mbtowc_l() (non-std! and no locale!!) and
289 * TODO work char-wise! -> S-CText..
290 * TODO The real problem for STD compatibility is however that "in" is
291 * TODO already iconv(3) encoded to the target character set! We could
292 * TODO also solve it (very expensively!) if we would narrow down to an
293 * TODO encoded word and then iconv(3)+MIME encode in one go, in which
294 * TODO case multibyte errors could be catched! */
295 enum {
296 /* Maximum line length *//* XXX we are too inflexible and could use
297 * XXX MIME_LINELEN unless an RFC 2047 encoding was actually used */
298 _MAXCOL = MIME_LINELEN_RFC2047
301 struct str cout, cin;
302 enum {
303 _FIRST = 1<<0, /* Nothing written yet, start of string */
304 _MSH_NOTHING = 1<<1, /* Now, really: nothing at all has been written */
305 _NO_QP = 1<<2, /* No quoted-printable allowed */
306 _NO_B64 = 1<<3, /* Ditto, base64 */
307 _ENC_LAST = 1<<4, /* Last round generated encoded word */
308 _SHOULD_BEE = 1<<5, /* Avoid lines longer than SHOULD via encoding */
309 _RND_SHIFT = 6,
310 _RND_MASK = (1<<_RND_SHIFT) - 1,
311 _SPACE = 1<<(_RND_SHIFT+1), /* Leading whitespace */
312 _8BIT = 1<<(_RND_SHIFT+2), /* High bit set */
313 _ENCODE = 1<<(_RND_SHIFT+3), /* Need encoding */
314 _ENC_B64 = 1<<(_RND_SHIFT+4), /* - let it be base64 */
315 _OVERLONG = 1<<(_RND_SHIFT+5) /* Temporarily rised limit */
316 } flags;
317 char const *cset7, *cset8, *wbot, *upper, *wend, *wcur;
318 ui32_t cset7_len, cset8_len;
319 size_t col, i, j;
320 ssize_t sz;
322 NYD_ENTER;
324 cout.s = NULL, cout.l = 0;
325 cset7 = ok_vlook(charset_7bit);
326 cset7_len = (ui32_t)strlen(cset7);
327 cset8 = _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */
328 cset8_len = (ui32_t)strlen(cset8);
330 flags = _FIRST;
331 if(msh != a_MIME_SH_NONE)
332 flags |= _MSH_NOTHING;
334 /* RFC 1468, "MIME Considerations":
335 * ISO-2022-JP may also be used in MIME Part 2 headers. The "B"
336 * encoding should be used with ISO-2022-JP text. */
337 /* TODO of course, our current implementation won't deal properly with
338 * TODO any stateful encoding at all... (the standard says each encoded
339 * TODO word must include all necessary reset sequences..., i.e., each
340 * TODO encoded word must be a self-contained iconv(3) life cycle) */
341 if (!asccasecmp(cset8, "iso-2022-jp") || mime_enc_target() == MIMEE_B64)
342 flags |= _NO_QP;
344 wbot = in->s;
345 upper = wbot + in->l;
347 if(colp == NULL || (col = *colp) == 0)
348 col = sizeof("Mail-Followup-To: ") -1; /* dreadful thing */
350 for (sz = 0; wbot < upper; flags &= ~_FIRST, wbot = wend) {
351 flags &= _RND_MASK;
352 wcur = wbot;
353 while (wcur < upper && whitechar(*wcur)) {
354 flags |= _SPACE;
355 ++wcur;
358 /* Any occurrence of whitespace resets prevention of lines >SHOULD via
359 * enforced encoding (xxx SHOULD, but.. encoding is expensive!!) */
360 if (flags & _SPACE)
361 flags &= ~_SHOULD_BEE;
363 /* Data ends with WS - dump it and done.
364 * Also, if we have seen multiple successive whitespace characters, then
365 * if there was no encoded word last, i.e., if we can simply take them
366 * over to the output as-is, keep one WS for possible later separation
367 * purposes and simply print the others as-is, directly! */
368 if (wcur == upper) {
369 wend = wcur;
370 goto jnoenc_putws;
372 if ((flags & (_ENC_LAST | _SPACE)) == _SPACE && wcur - wbot > 1) {
373 wend = wcur - 1;
374 goto jnoenc_putws;
377 /* Skip over a word to next non-whitespace, keep track along the way
378 * whether our 7-bit charset suffices to represent the data */
379 for (wend = wcur; wend < upper; ++wend) {
380 if (whitechar(*wend))
381 break;
382 if ((uc_i)*wend & 0x80)
383 flags |= _8BIT;
386 /* Decide whether the range has to become encoded or not */
387 i = PTR2SIZE(wend - wcur);
388 j = mime_enc_mustquote(wcur, i, MIMEEF_ISHEAD);
389 /* If it just cannot fit on a SHOULD line length, force encode */
390 if (i >= _MAXCOL) {
391 flags |= _SHOULD_BEE; /* (Sigh: SHOULD only, not MUST..) */
392 goto j_beejump;
394 if ((flags & _SHOULD_BEE) || j > 0) {
395 j_beejump:
396 flags |= _ENCODE;
397 /* Use base64 if requested or more than 50% -37.5-% of the bytes of
398 * the string need to be encoded */
399 if ((flags & _NO_QP) || j >= i >> 1)/*(i >> 2) + (i >> 3))*/
400 flags |= _ENC_B64;
402 DBG( if (flags & _8BIT) assert(flags & _ENCODE); )
404 if (!(flags & _ENCODE)) {
405 /* Encoded word produced, but no linear whitespace for necessary RFC
406 * 2047 separation? Generate artificial data (bad standard!) */
407 if ((flags & (_ENC_LAST | _SPACE)) == _ENC_LAST) {
408 if (col >= _MAXCOL) {
409 putc('\n', fo);
410 ++sz;
411 col = 0;
413 if(flags & _MSH_NOTHING){
414 flags &= ~_MSH_NOTHING;
415 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
416 ++sz;
417 ++col;
419 putc(' ', fo);
420 ++sz;
421 ++col;
424 jnoenc_putws:
425 flags &= ~_ENC_LAST;
427 /* todo No effort here: (1) v15.0 has to bring complete rewrite,
428 * todo (2) the standard is braindead and (3) usually this is one
429 * todo word only, and why be smarter than the standard? */
430 jnoenc_retry:
431 i = PTR2SIZE(wend - wbot);
432 if (i + col <= (flags & _OVERLONG ? MIME_LINELEN_MAX : _MAXCOL)) {
433 if(flags & _MSH_NOTHING){
434 flags &= ~_MSH_NOTHING;
435 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
436 ++sz;
437 ++col;
439 i = fwrite(wbot, sizeof *wbot, i, fo);
440 sz += i;
441 col += i;
442 continue;
445 /* Doesn't fit, try to break the line first; */
446 if (col > 1) {
447 putc('\n', fo);
448 if (whitechar(*wbot)) {
449 putc((uc_i)*wbot, fo);
450 ++wbot;
451 } else
452 putc(' ', fo); /* Bad standard: artificial data! */
453 sz += 2;
454 col = 1;
455 if(flags & _MSH_NOTHING){
456 flags &= ~_MSH_NOTHING;
457 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
458 ++sz;
459 ++col;
461 flags |= _OVERLONG;
462 goto jnoenc_retry;
465 /* It is so long that it needs to be broken, effectively causing
466 * artificial spaces to be inserted (bad standard), yuck */
467 /* todo This is not multibyte safe, as above; and completely stupid
468 * todo P.S.: our _SHOULD_BEE prevents these cases in the meanwhile */
469 /* FIXME OPT_UNICODE and parse using UTF-8 sync possibility! */
470 wcur = wbot + MIME_LINELEN_MAX - 8;
471 while (wend > wcur)
472 wend -= 4;
473 goto jnoenc_retry;
474 } else {
475 /* Encoding to encoded word(s); deal with leading whitespace, place
476 * a separator first as necessary: encoded words must always be
477 * separated from text and other encoded words with linear WS.
478 * And if an encoded word was last, intermediate whitespace must
479 * also be encoded, otherwise it would get stripped away! */
480 wcur = n_UNCONST(n_empty);
481 if ((flags & (_ENC_LAST | _SPACE)) != _SPACE) {
482 /* Reinclude whitespace */
483 flags &= ~_SPACE;
484 /* We don't need to place a separator at the very beginning */
485 if (!(flags & _FIRST))
486 wcur = n_UNCONST(" ");
487 } else
488 wcur = wbot++;
490 flags |= _ENC_LAST;
491 pstate |= PS_HEADER_NEEDED_MIME;
493 /* RFC 2047:
494 * An 'encoded-word' may not be more than 75 characters long,
495 * including 'charset', 'encoding', 'encoded-text', and
496 * delimiters. If it is desirable to encode more text than will
497 * fit in an 'encoded-word' of 75 characters, multiple
498 * 'encoded-word's (separated by CRLF SPACE) may be used.
500 * While there is no limit to the length of a multiple-line
501 * header field, each line of a header field that contains one
502 * or more 'encoded-word's is limited to 76 characters */
503 jenc_retry:
504 cin.s = n_UNCONST(wbot);
505 cin.l = PTR2SIZE(wend - wbot);
507 /* C99 */{
508 struct str *xout;
510 if(flags & _ENC_B64)
511 xout = b64_encode(&cout, &cin, B64_ISHEAD | B64_ISENCWORD);
512 else
513 xout = qp_encode(&cout, &cin, QP_ISHEAD | QP_ISENCWORD);
514 if(xout == NULL){
515 sz = -1;
516 break;
518 j = xout->l;
520 /* (Avoid trigraphs in the RFC 2047 placeholder..) */
521 i = j + (flags & _8BIT ? cset8_len : cset7_len) + sizeof("=!!B!!=") -1;
522 if (*wcur != '\0')
523 ++i;
525 jenc_retry_same:
526 /* Unfortunately RFC 2047 explicitly disallows encoded words to be
527 * longer (just like RFC 5322's "a line SHOULD fit in 78 but MAY be
528 * 998 characters long"), so we cannot use the _OVERLONG mechanism,
529 * even though all tested mailers seem to support it */
530 if (i + col <= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ _MAXCOL)) {
531 if(flags & _MSH_NOTHING){
532 flags &= ~_MSH_NOTHING;
533 putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
534 ++sz;
535 ++col;
537 fprintf(fo, "%.1s=?%s?%c?%.*s?=",
538 wcur, (flags & _8BIT ? cset8 : cset7),
539 (flags & _ENC_B64 ? 'B' : 'Q'),
540 (int)cout.l, cout.s);
541 sz += i;
542 col += i;
543 continue;
546 /* Doesn't fit, try to break the line first */
547 /* TODO I've commented out the _FIRST test since we (1) cannot do
548 * TODO _OVERLONG since (MUAs support but) the standard disallows,
549 * TODO and because of our iconv problem i prefer an empty first line
550 * TODO in favour of a possibly messed up multibytes character. :-( */
551 if (col > 1 /* TODO && !(flags & _FIRST)*/) {
552 putc('\n', fo);
553 sz += 2;
554 col = 1;
555 if (!(flags & _SPACE)) {
556 putc(' ', fo);
557 wcur = n_UNCONST(n_empty);
558 /*flags |= _OVERLONG;*/
559 goto jenc_retry_same;
560 } else {
561 putc((uc_i)*wcur, fo);
562 if (whitechar(*(wcur = wbot)))
563 ++wbot;
564 else {
565 flags &= ~_SPACE;
566 wcur = n_UNCONST(n_empty);
568 /*flags &= ~_OVERLONG;*/
569 goto jenc_retry;
573 /* It is so long that it needs to be broken, effectively causing
574 * artificial data to be inserted (bad standard), yuck */
575 /* todo This is not multibyte safe, as above */
576 /*if (!(flags & _OVERLONG)) { Mechanism explicitly forbidden by 2047
577 flags |= _OVERLONG;
578 goto jenc_retry;
581 /* FIXME OPT_UNICODE and parse using UTF-8 sync possibility! */
582 i = PTR2SIZE(wend - wbot) + !!(flags & _SPACE);
583 j = 3 + !(flags & _ENC_B64);
584 for (;;) {
585 wend -= j;
586 i -= j;
587 /* (Note the problem most likely is the transfer-encoding blow,
588 * which is why we test this *after* the decrements.. */
589 if (i <= _MAXCOL)
590 break;
592 goto jenc_retry;
596 if(!(flags & _MSH_NOTHING) && msh != a_MIME_SH_NONE){
597 putc((msh == a_MIME_SH_COMMENT ? ')' : '"'), fo);
598 ++sz;
599 ++col;
602 if (cout.s != NULL)
603 free(cout.s);
605 if(colp != NULL)
606 *colp = col;
607 NYD_LEAVE;
608 return sz;
611 static ssize_t
612 mime_write_tohdr_a(struct str *in, FILE *f, size_t *colp)
614 struct str xin;
615 size_t i;
616 char const *cp, *lastcp;
617 ssize_t sz, x;
618 NYD_ENTER;
620 in->s[in->l] = '\0';
621 lastcp = in->s;
622 if((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
623 xin.s = in->s;
624 xin.l = PTR2SIZE(cp - in->s);
625 if ((sz = mime_write_tohdr_a(&xin, f, colp)) < 0)
626 goto jleave;
627 xin.s[xin.l] = '<';
628 lastcp = cp;
629 } else {
630 cp = in->s;
631 sz = 0;
634 for( ; *cp != '\0'; ++cp){
635 switch(*cp){
636 case '(':
637 i = PTR2SIZE(cp - lastcp);
638 if(i > 0){
639 if(fwrite(lastcp, 1, i, f) != i)
640 goto jerr;
641 sz += i;
643 lastcp = ++cp;
644 cp = skip_comment(cp);
645 if(--cp > lastcp){
646 i = PTR2SIZE(cp - lastcp);
647 xin.s = n_UNCONST(lastcp);
648 xin.l = i;
649 if ((x = a_mime__convhdra(&xin, f, colp, a_MIME_SH_COMMENT)) < 0)
650 goto jerr;
651 sz += x;
653 lastcp = &cp[1];
654 break;
655 case '"':
656 i = PTR2SIZE(cp - lastcp);
657 if(i > 0){
658 if(fwrite(lastcp, 1, i, f) != i)
659 goto jerr;
660 sz += i;
662 for(lastcp = ++cp; *cp != '\0'; ++cp){
663 if(*cp == '"')
664 break;
665 if(*cp == '\\' && cp[1] != '\0')
666 ++cp;
668 i = PTR2SIZE(cp - lastcp);
669 if(i > 0){
670 xin.s = n_UNCONST(lastcp);
671 xin.l = i;
672 if((x = a_mime__convhdra(&xin, f, colp, a_MIME_SH_QUOTE)) < 0)
673 goto jerr;
674 sz += x;
676 ++sz;
677 lastcp = &cp[1];
678 break;
682 i = PTR2SIZE(cp - lastcp);
683 if(i > 0){
684 if(fwrite(lastcp, 1, i, f) != i)
685 goto jerr;
686 sz += i;
688 jleave:
689 NYD_LEAVE;
690 return sz;
691 jerr:
692 sz = -1;
693 goto jleave;
696 #ifdef HAVE_ICONV
697 static ssize_t
698 a_mime__convhdra(struct str *inp, FILE *fp, size_t *colp,
699 enum a_mime_structure_hack msh){
700 struct str ciconv;
701 ssize_t rv;
702 NYD_ENTER;
704 rv = 0;
705 ciconv.s = NULL;
707 if(iconvd != (iconv_t)-1){
708 ciconv.l = 0;
709 if(n_iconv_str(iconvd, n_ICONV_IGN_NOREVERSE, &ciconv, inp, NULL) != 0){
710 n_iconv_reset(iconvd);
711 goto jleave;
713 *inp = ciconv;
716 rv = mime_write_tohdr(inp, fp, colp, msh);
717 jleave:
718 if(ciconv.s != NULL)
719 free(ciconv.s);
720 NYD_LEAVE;
721 return rv;
723 #endif /* HAVE_ICONV */
725 static void
726 _append_str(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
728 NYD_ENTER;
729 *buf = srealloc(*buf, *sz += len);
730 memcpy(&(*buf)[*pos], str, len);
731 *pos += len;
732 NYD_LEAVE;
735 static void
736 _append_conv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
738 struct str in, out;
739 NYD_ENTER;
741 in.s = n_UNCONST(str);
742 in.l = len;
743 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
744 _append_str(buf, sz, pos, out.s, out.l);
745 free(out.s);
746 NYD_LEAVE;
749 FL bool_t
750 charset_iter_reset(char const *a_charset_to_try_first) /* TODO elim. dups! */
752 char const *sarr[3];
753 size_t sarrl[3], len;
754 char *cp;
755 NYD_ENTER;
756 n_UNUSED(a_charset_to_try_first);
758 #ifdef HAVE_ICONV
759 sarr[2] = ok_vlook(CHARSET_8BIT_OKEY);
761 if(a_charset_to_try_first != NULL && strcmp(a_charset_to_try_first, sarr[2]))
762 sarr[0] = a_charset_to_try_first;
763 else
764 sarr[0] = NULL;
766 if((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
767 ok_blook(sendcharsets_else_ttycharset)){
768 cp = n_UNCONST(ok_vlook(ttycharset));
769 if(strcmp(cp, sarr[2]) && (sarr[0] == NULL || strcmp(cp, sarr[0])))
770 sarr[1] = cp;
772 #else
773 sarr[2] = ok_vlook(ttycharset);
774 #endif
776 sarrl[2] = len = strlen(sarr[2]);
777 #ifdef HAVE_ICONV
778 if ((cp = n_UNCONST(sarr[1])) != NULL)
779 len += (sarrl[1] = strlen(cp));
780 else
781 sarrl[1] = 0;
782 if ((cp = n_UNCONST(sarr[0])) != NULL)
783 len += (sarrl[0] = strlen(cp));
784 else
785 sarrl[0] = 0;
786 #endif
788 _cs_iter_base = cp = salloc(len + 1 + 1 +1);
790 #ifdef HAVE_ICONV
791 if ((len = sarrl[0]) != 0) {
792 memcpy(cp, sarr[0], len);
793 cp[len] = ',';
794 cp += ++len;
796 if ((len = sarrl[1]) != 0) {
797 memcpy(cp, sarr[1], len);
798 cp[len] = ',';
799 cp += ++len;
801 #endif
802 len = sarrl[2];
803 memcpy(cp, sarr[2], len);
804 cp[len] = '\0';
806 _CS_ITER_STEP();
807 NYD_LEAVE;
808 return (_cs_iter != NULL);
811 FL bool_t
812 charset_iter_next(void)
814 bool_t rv;
815 NYD_ENTER;
817 _CS_ITER_STEP();
818 rv = (_cs_iter != NULL);
819 NYD_LEAVE;
820 return rv;
823 FL bool_t
824 charset_iter_is_valid(void)
826 bool_t rv;
827 NYD_ENTER;
829 rv = (_cs_iter != NULL);
830 NYD_LEAVE;
831 return rv;
834 FL char const *
835 charset_iter(void)
837 char const *rv;
838 NYD_ENTER;
840 rv = _cs_iter;
841 NYD_LEAVE;
842 return rv;
845 FL char const *
846 charset_iter_or_fallback(void)
848 char const *rv;
849 NYD_ENTER;
851 rv = _CS_ITER_GET();
852 NYD_LEAVE;
853 return rv;
856 FL void
857 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
859 NYD_ENTER;
860 outer_storage[0] = _cs_iter_base;
861 outer_storage[1] = _cs_iter;
862 NYD_LEAVE;
865 FL void
866 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
868 NYD_ENTER;
869 _cs_iter_base = outer_storage[0];
870 _cs_iter = outer_storage[1];
871 NYD_LEAVE;
874 #ifdef HAVE_ICONV
875 FL char const *
876 need_hdrconv(struct header *hp) /* TODO once only, then iter */
878 struct n_header_field *hfp;
879 char const *rv;
880 NYD_ENTER;
882 rv = NULL;
884 if((hfp = hp->h_user_headers) != NULL)
885 do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
886 goto jneeds;
887 while((hfp = hfp->hf_next) != NULL);
889 if((hfp = hp->h_custom_headers) != NULL ||
890 (hp->h_custom_headers = hfp = n_customhdr_query()) != NULL)
891 do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
892 goto jneeds;
893 while((hfp = hfp->hf_next) != NULL);
895 if (hp->h_mft != NULL) {
896 if (_name_highbit(hp->h_mft))
897 goto jneeds;
899 if (hp->h_from != NULL) {
900 if (_name_highbit(hp->h_from))
901 goto jneeds;
902 } else if (_has_highbit(myaddrs(NULL)))
903 goto jneeds;
904 if (hp->h_replyto) {
905 if (_name_highbit(hp->h_replyto))
906 goto jneeds;
907 } else if (_has_highbit(ok_vlook(replyto)))
908 goto jneeds;
909 if (hp->h_sender) {
910 if (_name_highbit(hp->h_sender))
911 goto jneeds;
912 } else if (_has_highbit(ok_vlook(sender)))
913 goto jneeds;
915 if (_name_highbit(hp->h_to))
916 goto jneeds;
917 if (_name_highbit(hp->h_cc))
918 goto jneeds;
919 if (_name_highbit(hp->h_bcc))
920 goto jneeds;
921 if (_has_highbit(hp->h_subject))
922 jneeds:
923 rv = _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
924 NYD_LEAVE;
925 return rv;
927 #endif /* HAVE_ICONV */
929 FL void
930 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
932 /* TODO mime_fromhdr(): is called with strings that contain newlines;
933 * TODO this is the usual newline problem all around the codebase;
934 * TODO i.e., if we strip it, then the display misses it ;>
935 * TODO this is why it is so messy and why S-nail v14.2 plus additional
936 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
937 * TODO why our display reflects what is contained in the message: the 1:1
938 * TODO relationship of message content and display!
939 * TODO instead a header line should be decoded to what it is (a single
940 * TODO line that is) and it should be objective to the backend whether
941 * TODO it'll be folded to fit onto the display or not, e.g., for search
942 * TODO purposes etc. then the only condition we have to honour in here
943 * TODO is that whitespace in between multiple adjacent MIME encoded words
944 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
945 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
946 struct str cin, cout;
947 char *p, *op, *upper;
948 ui32_t convert, lastenc, lastoutl;
949 #ifdef HAVE_ICONV
950 char const *tcs;
951 char *cbeg;
952 iconv_t fhicd = (iconv_t)-1;
953 #endif
954 NYD_ENTER;
956 out->l = 0;
957 if (in->l == 0) {
958 *(out->s = smalloc(1)) = '\0';
959 goto jleave;
961 out->s = NULL;
963 #ifdef HAVE_ICONV
964 tcs = ok_vlook(ttycharset);
965 #endif
966 p = in->s;
967 upper = p + in->l;
968 lastenc = lastoutl = 0;
970 while (p < upper) {
971 op = p;
972 if (*p == '=' && *(p + 1) == '?') {
973 p += 2;
974 #ifdef HAVE_ICONV
975 cbeg = p;
976 #endif
977 while (p < upper && *p != '?')
978 ++p; /* strip charset */
979 if (p >= upper)
980 goto jnotmime;
981 ++p;
982 #ifdef HAVE_ICONV
983 if (flags & TD_ICONV) {
984 size_t i = PTR2SIZE(p - cbeg);
985 char *ltag, *cs = ac_alloc(i);
987 memcpy(cs, cbeg, --i);
988 cs[i] = '\0';
989 /* RFC 2231 extends the RFC 2047 character set definition in
990 * encoded words by language tags - silently strip those off */
991 if ((ltag = strchr(cs, '*')) != NULL)
992 *ltag = '\0';
994 if (fhicd != (iconv_t)-1)
995 n_iconv_close(fhicd);
996 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
997 ac_free(cs);
999 #endif
1000 switch (*p) {
1001 case 'B': case 'b':
1002 convert = CONV_FROMB64;
1003 break;
1004 case 'Q': case 'q':
1005 convert = CONV_FROMQP;
1006 break;
1007 default: /* invalid, ignore */
1008 goto jnotmime;
1010 if (*++p != '?')
1011 goto jnotmime;
1012 cin.s = ++p;
1013 cin.l = 1;
1014 for (;;) {
1015 if (PTRCMP(p + 1, >=, upper))
1016 goto jnotmime;
1017 if (*p++ == '?' && *p == '=')
1018 break;
1019 ++cin.l;
1021 ++p;
1022 --cin.l;
1024 cout.s = NULL;
1025 cout.l = 0;
1026 if (convert == CONV_FROMB64) {
1027 if(!b64_decode_header(&cout, &cin))
1028 n_str_assign_cp(&cout, _("[Invalid Base64 encoding]"));
1029 }else if(!qp_decode_header(&cout, &cin))
1030 n_str_assign_cp(&cout, _("[Invalid Quoted-Printable encoding]"));
1032 out->l = lastenc;
1033 #ifdef HAVE_ICONV
1034 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
1035 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
1036 convert = n_iconv_str(fhicd, n_ICONV_UNIDEFAULT, &cin, &cout, NULL);
1037 out = n_str_add(out, &cin);
1038 if (convert) {/* EINVAL at EOS */
1039 n_iconv_reset(fhicd);
1040 out = n_str_add_buf(out, "?", 1); /* TODO unicode replacement */
1042 free(cin.s);
1043 } else
1044 #endif
1045 out = n_str_add(out, &cout);
1046 lastenc = lastoutl = out->l;
1047 free(cout.s);
1048 } else
1049 jnotmime: {
1050 bool_t onlyws;
1052 p = op;
1053 onlyws = (lastenc > 0);
1054 for (;;) {
1055 if (++op == upper)
1056 break;
1057 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
1058 break;
1059 if (onlyws && !blankchar(*op))
1060 onlyws = FAL0;
1063 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
1064 p = op;
1065 if (!onlyws || lastoutl != lastenc)
1066 lastenc = out->l;
1067 lastoutl = out->l;
1070 out->s[out->l] = '\0';
1072 if (flags & TD_ISPR) {
1073 makeprint(out, &cout);
1074 free(out->s);
1075 *out = cout;
1077 if (flags & TD_DELCTRL)
1078 out->l = delctrl(out->s, out->l);
1079 #ifdef HAVE_ICONV
1080 if (fhicd != (iconv_t)-1)
1081 n_iconv_close(fhicd);
1082 #endif
1083 jleave:
1084 NYD_LEAVE;
1085 return;
1088 FL char *
1089 mime_fromaddr(char const *name)
1091 char const *cp, *lastcp;
1092 char *res = NULL;
1093 size_t ressz = 1, rescur = 0;
1094 NYD_ENTER;
1096 if (name == NULL)
1097 goto jleave;
1098 if (*name == '\0') {
1099 res = savestr(name);
1100 goto jleave;
1103 if ((cp = routeaddr(name)) != NULL && cp > name) {
1104 _append_conv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
1105 lastcp = cp;
1106 } else
1107 cp = lastcp = name;
1109 for ( ; *cp; ++cp) {
1110 switch (*cp) {
1111 case '(':
1112 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
1113 lastcp = ++cp;
1114 cp = skip_comment(cp);
1115 if (--cp > lastcp)
1116 _append_conv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1117 lastcp = cp;
1118 break;
1119 case '"':
1120 while (*cp) {
1121 if (*++cp == '"')
1122 break;
1123 if (*cp == '\\' && cp[1] != '\0')
1124 ++cp;
1126 break;
1129 if (cp > lastcp)
1130 _append_str(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1131 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1132 if (rescur == 0)
1133 res = n_UNCONST(n_empty);
1134 else
1135 res[rescur] = '\0';
1136 { char *x = res;
1137 res = savestr(res);
1138 free(x);
1140 jleave:
1141 NYD_LEAVE;
1142 return res;
1145 FL ssize_t
1146 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1147 enum tdflags dflags)
1149 ssize_t rv;
1150 struct quoteflt *qf;
1151 NYD_ENTER;
1153 quoteflt_reset(qf = quoteflt_dummy(), f);
1154 rv = mime_write(ptr, size, f, convert, dflags, qf, NULL, NULL);
1155 quoteflt_flush(qf);
1156 NYD_LEAVE;
1157 return rv;
1160 static sigjmp_buf __mimemw_actjmp; /* TODO someday.. */
1161 static int __mimemw_sig; /* TODO someday.. */
1162 static sighandler_type __mimemw_opipe;
1163 static void
1164 __mimemw_onsig(int sig) /* TODO someday, we won't need it no more */
1166 NYD_X; /* Signal handler */
1167 __mimemw_sig = sig;
1168 siglongjmp(__mimemw_actjmp, 1);
1171 FL ssize_t
1172 mime_write(char const *ptr, size_t size, FILE *f,
1173 enum conversion convert, enum tdflags volatile dflags,
1174 struct quoteflt *qf, struct str * volatile outrest,
1175 struct str * volatile inrest)
1177 /* TODO note: after send/MIME layer rewrite we will have a string pool
1178 * TODO so that memory allocation count drops down massively; for now,
1179 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1180 struct str in, out;
1181 ssize_t volatile sz;
1182 NYD_ENTER;
1184 dflags |= _TD_BUFCOPY;
1185 in.s = n_UNCONST(ptr);
1186 in.l = size;
1188 if(inrest != NULL && inrest->l > 0){
1189 out.s = smalloc(inrest->l + size + 1);
1190 memcpy(out.s, inrest->s, inrest->l);
1191 if(size > 0)
1192 memcpy(&out.s[inrest->l], in.s, size);
1193 size += inrest->l;
1194 inrest->l = 0;
1195 (in.s = out.s)[in.l = size] = '\0';
1196 dflags &= ~_TD_BUFCOPY;
1199 out.s = NULL;
1200 out.l = 0;
1202 if ((sz = size) == 0) {
1203 if (outrest != NULL && outrest->l != 0)
1204 goto jconvert;
1205 goto jleave;
1208 #ifdef HAVE_ICONV
1209 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1210 (convert == CONV_TOQP || convert == CONV_8BIT ||
1211 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1212 if (n_iconv_str(iconvd, n_ICONV_IGN_NOREVERSE, &out, &in, NULL) != 0) {
1213 n_iconv_reset(iconvd);
1214 /* TODO This causes hard-failure. We would need to have an action
1215 * TODO policy FAIL|IGNORE|SETERROR(but continue). Better huh? */
1216 sz = -1;
1217 goto jleave;
1219 in = out;
1220 out.s = NULL;
1221 dflags &= ~_TD_BUFCOPY;
1223 #endif
1225 jconvert:
1226 __mimemw_sig = 0;
1227 __mimemw_opipe = safe_signal(SIGPIPE, &__mimemw_onsig);
1228 if (sigsetjmp(__mimemw_actjmp, 1))
1229 goto jleave;
1231 switch (convert) {
1232 case CONV_FROMQP:
1233 if(!qp_decode_part(&out, &in, outrest, inrest)){
1234 n_err(_("Invalid Quoted-Printable encoding ignored\n"));
1235 sz = 0; /* TODO sz = -1 stops outer levels! */
1236 break;
1238 goto jqpb64_dec;
1239 case CONV_TOQP:
1240 if(qp_encode(&out, &in, QP_NONE) == NULL){
1241 sz = 0; /* TODO sz = -1 stops outer levels! */
1242 break;
1244 goto jqpb64_enc;
1245 case CONV_8BIT:
1246 sz = quoteflt_push(qf, in.s, in.l);
1247 break;
1248 case CONV_FROMB64:
1249 if(!b64_decode_part(&out, &in, outrest, inrest))
1250 goto jeb64;
1251 outrest = NULL;
1252 if(0){
1253 /* FALLTHRU */
1254 case CONV_FROMB64_T:
1255 if(!b64_decode_part(&out, &in, outrest, inrest)){
1256 jeb64:
1257 n_err(_("Invalid Base64 encoding ignored\n"));
1258 sz = 0; /* TODO sz = -1 stops outer levels! */
1259 break;
1262 jqpb64_dec:
1263 if ((sz = out.l) != 0) {
1264 ui32_t opl = qf->qf_pfix_len;
1265 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), outrest, qf);
1266 qf->qf_pfix_len = opl;
1268 break;
1269 case CONV_TOB64:
1270 if(b64_encode(&out, &in, B64_LF | B64_MULTILINE) == NULL){
1271 sz = -1;
1272 break;
1274 jqpb64_enc:
1275 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1276 if (sz != (ssize_t)out.l)
1277 sz = -1;
1278 break;
1279 case CONV_FROMHDR:
1280 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1281 sz = quoteflt_push(qf, out.s, out.l);
1282 break;
1283 case CONV_TOHDR:
1284 sz = mime_write_tohdr(&in, f, NULL, a_MIME_SH_NONE);
1285 break;
1286 case CONV_TOHDR_A:{
1287 size_t col;
1289 if(dflags & _TD_BUFCOPY){
1290 n_str_dup(&out, &in);
1291 in = out;
1292 out.s = NULL;
1293 dflags &= ~_TD_BUFCOPY;
1295 col = 0;
1296 sz = mime_write_tohdr_a(&in, f, &col);
1297 } break;
1298 default:
1299 sz = _fwrite_td(&in, dflags, NULL, qf);
1300 break;
1302 jleave:
1303 if (out.s != NULL)
1304 free(out.s);
1305 if (in.s != ptr)
1306 free(in.s);
1307 safe_signal(SIGPIPE, __mimemw_opipe);
1308 if (__mimemw_sig != 0)
1309 n_raise(__mimemw_sig);
1310 NYD_LEAVE;
1311 return sz;
1314 /* s-it-mode */