Merge branch 'topic/mdn'
[s-mailx.git] / mime.c
blob14cdfc54face9582f994e577c3d2d3cf79badb5c
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 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 2000
9 * Gunnar Ritter. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by Gunnar Ritter
22 * and his contributors.
23 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 struct mtnode {
45 struct mtnode *mt_next;
46 size_t mt_mtlen; /* Length of MIME type string */
47 char mt_line[VFIELD_SIZE(8)];
50 static char const * const _mt_sources[] = {
51 /* XXX Order fixed due to *mimetypes-load-control* handling! */
52 MIME_TYPES_USR, MIME_TYPES_SYS, NULL
54 * const _mt_bltin[] = {
55 #include "mime_types.h"
56 NULL
59 static struct mtnode *_mt_list;
60 static char *_cs_iter_base, *_cs_iter;
62 #ifdef HAVE_ICONV
63 # define _CS_ITER_GET() ((_cs_iter != NULL) ? _cs_iter : charset_get_8bit())
64 #else
65 # define _CS_ITER_GET() ((_cs_iter != NULL) ? _cs_iter : charset_get_lc())
66 #endif
67 #define _CS_ITER_STEP() _cs_iter = n_strsep(&_cs_iter_base, ',', TRU1)
69 /* Initialize MIME type list */
70 static void _mt_init(void);
71 static void __mt_add_line(char const *line, struct mtnode **tail);
73 /* Is 7-bit enough? */
74 #ifdef HAVE_ICONV
75 static bool_t _has_highbit(char const *s);
76 static bool_t _name_highbit(struct name *np);
77 #endif
79 /* Get the conversion that matches *encoding* */
80 static enum conversion _conversion_by_encoding(void);
82 /* fwrite(3) while checking for displayability */
83 static ssize_t _fwrite_td(struct str const *input, enum tdflags flags,
84 struct str *rest, struct quoteflt *qf);
86 static size_t delctrl(char *cp, size_t sz);
88 static int is_this_enc(char const *line, char const *encoding);
90 /* Convert header fields to RFC 1522 format and write to the file fo */
91 static ssize_t mime_write_tohdr(struct str *in, FILE *fo);
93 /* Write len characters of the passed string to the passed file, doing charset
94 * and header conversion */
95 static ssize_t convhdra(char const *str, size_t len, FILE *fp);
97 /* Write an address to a header field */
98 static ssize_t mime_write_tohdr_a(struct str *in, FILE *f);
100 /* Append to buf, handling resizing */
101 static void addstr(char **buf, size_t *sz, size_t *pos,
102 char const *str, size_t len);
104 static void addconv(char **buf, size_t *sz, size_t *pos,
105 char const *str, size_t len);
107 static void
108 _mt_init(void)
110 struct mtnode *tail = NULL;
111 char *line = NULL; /* TODO line pool */
112 size_t linesize = 0;
113 ui32_t idx, idx_ok;
114 char const *ccp, * const *srcs;
115 FILE *fp;
116 NYD_ENTER;
118 if ((ccp = ok_vlook(mimetypes_load_control)) == NULL)
119 idx_ok = (ui32_t)-1;
120 else for (idx_ok = 0; *ccp != '\0'; ++ccp)
121 switch (*ccp) {
122 case 'S':
123 case 's':
124 idx_ok |= 1 << 1;
125 break;
126 case 'U':
127 case 'u':
128 idx_ok |= 1 << 0;
129 break;
130 default:
131 /* XXX bad *mimetypes-load-control*; log error? */
132 break;
135 for (idx = 1, srcs = _mt_sources; *srcs != NULL; idx <<= 1, ++srcs) {
136 if (!(idx & idx_ok) || (ccp = file_expand(*srcs)) == NULL)
137 continue;
138 if ((fp = Fopen(ccp, "r")) == NULL) {
139 /*fprintf(stderr, tr(176, "Cannot open `%s'\n"), fn);*/
140 continue;
142 while (fgetline(&line, &linesize, NULL, NULL, fp, 0))
143 __mt_add_line(line, &tail);
144 Fclose(fp);
146 if (line != NULL)
147 free(line);
149 for (srcs = _mt_bltin; *srcs != NULL; ++srcs)
150 __mt_add_line(*srcs, &tail);
151 NYD_LEAVE;
154 static void
155 __mt_add_line(char const *line, struct mtnode **tail) /* XXX diag? dups!*/
157 char const *typ;
158 size_t tlen, elen;
159 struct mtnode *mtn;
160 NYD_ENTER;
162 if (!alphachar(*line))
163 goto jleave;
165 typ = line;
166 while (!blankchar(*line) && *line != '\0')
167 ++line;
168 if (*line == '\0')
169 goto jleave;
170 tlen = PTR2SIZE(line - typ);
172 while (blankchar(*line) && *line != '\0')
173 ++line;
175 if (*line == '\0')
176 goto jleave;
177 elen = strlen(line);
178 if (line[elen - 1] == '\n') {
179 if (--elen > 0 && line[elen - 1] == '\r')
180 --elen;
181 if (elen == 0)
182 goto jleave;
185 mtn = smalloc(sizeof(struct mtnode) -
186 VFIELD_SIZEOF(struct mtnode, mt_line) + tlen + 1 + elen +1);
187 if (*tail != NULL)
188 (*tail)->mt_next = mtn;
189 else
190 _mt_list = mtn;
191 *tail = mtn;
192 mtn->mt_next = NULL;
193 mtn->mt_mtlen = tlen;
194 memcpy(mtn->mt_line, typ, tlen);
195 mtn->mt_line[tlen] = '\0';
196 ++tlen;
197 memcpy(mtn->mt_line + tlen, line, elen);
198 tlen += elen;
199 mtn->mt_line[tlen] = '\0';
200 jleave:
201 NYD_LEAVE;
204 #ifdef HAVE_ICONV
205 static bool_t
206 _has_highbit(char const *s)
208 bool_t rv = TRU1;
209 NYD_ENTER;
211 if (s) {
213 if ((ui8_t)*s & 0x80)
214 goto jleave;
215 while (*s++ != '\0');
217 rv = FAL0;
218 jleave:
219 NYD_LEAVE;
220 return rv;
223 static bool_t
224 _name_highbit(struct name *np)
226 bool_t rv = TRU1;
227 NYD_ENTER;
229 while (np) {
230 if (_has_highbit(np->n_name) || _has_highbit(np->n_fullname))
231 goto jleave;
232 np = np->n_flink;
234 rv = FAL0;
235 jleave:
236 NYD_LEAVE;
237 return rv;
239 #endif /* HAVE_ICONV */
241 static enum conversion
242 _conversion_by_encoding(void)
244 char const *cp;
245 enum conversion ret;
246 NYD_ENTER;
248 if ((cp = ok_vlook(encoding)) == NULL)
249 ret = MIME_DEFAULT_ENCODING;
250 else if (!strcmp(cp, "quoted-printable"))
251 ret = CONV_TOQP;
252 else if (!strcmp(cp, "8bit"))
253 ret = CONV_8BIT;
254 else if (!strcmp(cp, "base64"))
255 ret = CONV_TOB64;
256 else {
257 fprintf(stderr, tr(177, "Warning: invalid encoding %s, using base64\n"),
258 cp);
259 ret = CONV_TOB64;
261 NYD_LEAVE;
262 return ret;
265 static ssize_t
266 _fwrite_td(struct str const *input, enum tdflags flags, struct str *rest,
267 struct quoteflt *qf)
269 /* TODO note: after send/MIME layer rewrite we will have a string pool
270 * TODO so that memory allocation count drops down massively; for now,
271 * TODO v14.* that is, we pay a lot & heavily depend on the allocator */
272 /* TODO well if we get a broken pipe here, and it happens to
273 * TODO happen pretty easy when sleeping in a full pipe buffer,
274 * TODO then the current codebase performs longjump away;
275 * TODO this leaves memory leaks behind ('think up to 3 per,
276 * TODO dep. upon alloca availability). For this to be fixed
277 * TODO we either need to get rid of the longjmp()s (tm) or
278 * TODO the storage must come from the outside or be tracked
279 * TODO in a carrier struct. Best both. But storage reuse
280 * TODO would be a bigbig win besides */
281 /* *input* _may_ point to non-modifyable buffer; but even then it only
282 * needs to be dup'ed away if we have to transform the content */
283 struct str in, out;
284 ssize_t rv;
285 NYD_ENTER;
286 UNUSED(rest);
288 in = *input;
289 out.s = NULL;
290 out.l = 0;
292 #ifdef HAVE_ICONV
293 if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
294 char *buf = NULL;
296 if (rest != NULL && rest->l > 0) {
297 in.l = rest->l + input->l;
298 in.s = buf = smalloc(in.l +1);
299 memcpy(in.s, rest->s, rest->l);
300 memcpy(in.s + rest->l, input->s, input->l);
301 rest->l = 0;
304 if (n_iconv_str(iconvd, &out, &in, &in, TRU1) != 0 && rest != NULL &&
305 in.l > 0) {
306 /* Incomplete multibyte at EOF is special */
307 if (flags & _TD_EOF) {
308 out.s = srealloc(out.s, out.l + 4);
309 /* TODO 0xFFFD out.s[out.l++] = '[';*/
310 out.s[out.l++] = '?'; /* TODO 0xFFFD !!! */
311 /* TODO 0xFFFD out.s[out.l++] = ']';*/
312 } else
313 n_str_add(rest, &in);
315 in = out;
316 out.s = NULL;
317 flags &= ~_TD_BUFCOPY;
319 if (buf != NULL)
320 free(buf);
322 #endif
324 if (flags & TD_ISPR)
325 makeprint(&in, &out);
326 else if (flags & _TD_BUFCOPY)
327 n_str_dup(&out, &in);
328 else
329 out = in;
330 if (flags & TD_DELCTRL)
331 out.l = delctrl(out.s, out.l);
333 rv = quoteflt_push(qf, out.s, out.l);
335 if (out.s != in.s)
336 free(out.s);
337 if (in.s != input->s)
338 free(in.s);
339 NYD_LEAVE;
340 return rv;
343 static size_t
344 delctrl(char *cp, size_t sz)
346 size_t x = 0, y = 0;
347 NYD_ENTER;
349 while (x < sz) {
350 if (!cntrlchar(cp[x]))
351 cp[y++] = cp[x];
352 ++x;
354 NYD_LEAVE;
355 return y;
358 static int
359 is_this_enc(char const *line, char const *encoding)
361 int rv, c, quoted = 0;
362 NYD_ENTER;
364 if (*line == '"')
365 quoted = 1, ++line;
366 rv = 0;
367 while (*line != '\0' && *encoding)
368 if ((c = *line++, lowerconv(c) != *encoding++))
369 goto jleave;
370 rv = 1;
371 if (quoted && *line == '"')
372 goto jleave;
373 if (*line == '\0' || whitechar(*line))
374 goto jleave;
375 rv = 0;
376 jleave:
377 NYD_LEAVE;
378 return rv;
381 static ssize_t
382 mime_write_tohdr(struct str *in, FILE *fo) /* TODO rewrite - FAST! */
384 struct str cin, cout;
385 char buf[B64_LINESIZE +1]; /* (No CR/LF used) */
386 char const *charset7, *charset, *upper, *wbeg, *wend, *lastspc,
387 *lastwordend = NULL;
388 size_t col = 0, quoteany, wr, charsetlen,
389 maxcol = 65 /* there is the header field's name, too */;
390 ssize_t sz = 0;
391 bool_t highbit, mustquote, broken;
392 NYD_ENTER;
394 charset7 = charset_get_7bit();
395 charset = _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */
396 wr = strlen(charset7);
397 charsetlen = strlen(charset);
398 charsetlen = MAX(charsetlen, wr);
399 upper = in->s + in->l;
401 /* xxx note this results in too much hits since =/? force quoting even
402 * xxx if they don't form =? etc. */
403 quoteany = mime_cte_mustquote(in->s, in->l, TRU1);
405 highbit = FAL0;
406 if (quoteany != 0)
407 for (wbeg = in->s; wbeg < upper; ++wbeg)
408 if ((ui8_t)*wbeg & 0x80) {
409 highbit = TRU1;
410 if (charset == NULL) {
411 sz = -1;
412 goto jleave;
414 break;
417 if (quoteany << 1 > in->l) {
418 /* Print the entire field in base64 */
419 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
420 wend = upper;
421 cin.s = UNCONST(wbeg);
422 for (;;) {
423 cin.l = PTR2SIZE(wend - wbeg);
424 if (cin.l * 4/3 + 7 + charsetlen < maxcol - col) {
425 cout.s = buf;
426 cout.l = sizeof buf;
427 wr = fprintf(fo, "=?%s?B?%s?=", (highbit ? charset : charset7),
428 b64_encode(&cout, &cin, B64_BUF)->s);
429 sz += wr;
430 col += wr;
431 if (wend < upper) {
432 fwrite("\n ", sizeof(char), 2, fo);
433 sz += 2;
434 col = 0;
435 maxcol = 76;
437 break;
438 } else {
439 if (col) {
440 fprintf(fo, "\n ");
441 sz += 2;
442 col = 0;
443 maxcol = 76;
444 } else
445 wend -= 4;
449 } else {
450 /* Print the field word-wise in quoted-printable */
451 broken = FAL0;
452 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
453 lastspc = NULL;
454 while (wbeg < upper && whitechar(*wbeg)) {
455 lastspc = lastspc ? lastspc : wbeg;
456 ++wbeg;
457 ++col;
458 broken = FAL0;
460 if (wbeg == upper) {
461 if (lastspc)
462 while (lastspc < wbeg) {
463 putc(*lastspc&0377, fo);
464 ++lastspc;
465 ++sz;
467 break;
470 if (lastspc != NULL)
471 broken = FAL0;
472 highbit = FAL0;
473 for (wend = wbeg; wend < upper && !whitechar(*wend); ++wend)
474 if ((ui8_t)*wend & 0x80)
475 highbit = TRU1;
476 mustquote = (mime_cte_mustquote(wbeg, PTR2SIZE(wend - wbeg), TRU1)
477 != 0);
479 if (mustquote || broken ||
480 (PTR2SIZE(wend - wbeg) >= 76-5 && quoteany)) {
481 for (cout.s = NULL;;) {
482 cin.s = UNCONST(lastwordend ? lastwordend : wbeg);
483 cin.l = PTR2SIZE(wend - cin.s);
484 qp_encode(&cout, &cin, QP_ISHEAD);
485 wr = cout.l + charsetlen + 7;
486 jqp_retest:
487 if (col <= maxcol && wr <= maxcol - col) {
488 if (lastspc) {
489 /* TODO because we included the WS in the encoded str,
490 * TODO put SP only??
491 * TODO RFC: "any 'linear-white-space' that separates
492 * TODO a pair of adjacent 'encoded-word's is ignored" */
493 putc(' ', fo);
494 ++sz;
495 ++col;
497 fprintf(fo, "=?%s?Q?%.*s?=",
498 (highbit ? charset : charset7), (int)cout.l, cout.s);
499 sz += wr;
500 col += wr;
501 break;
502 } else if (col > 1) {
503 /* TODO assuming SP separator, ignore *lastspc* !?? */
504 broken = TRU1;
505 if (lastspc != NULL) {
506 putc('\n', fo);
507 ++sz;
508 col = 0;
509 } else {
510 fputs("\n ", fo);
511 sz += 2;
512 col = 1;
514 maxcol = 76;
515 goto jqp_retest;
516 } else {
517 for (;;) { /* XXX */
518 wend -= 4;
519 assert(wend > wbeg);
520 if (wr - 4 < maxcol)
521 break;
522 wr -= 4;
526 if (cout.s != NULL)
527 free(cout.s);
528 lastwordend = wend;
529 } else {
530 if (col && PTR2SIZE(wend - wbeg) > maxcol - col) {
531 putc('\n', fo);
532 ++sz;
533 col = 0;
534 maxcol = 76;
535 if (lastspc == NULL) {
536 putc(' ', fo);
537 ++sz;
538 --maxcol;
539 } else
540 maxcol -= PTR2SIZE(wbeg - lastspc);
542 if (lastspc)
543 while (lastspc < wbeg) {
544 putc(*lastspc&0377, fo);
545 ++lastspc;
546 ++sz;
548 wr = fwrite(wbeg, sizeof *wbeg, PTR2SIZE(wend - wbeg), fo);
549 sz += wr;
550 col += wr;
551 lastwordend = NULL;
555 jleave:
556 NYD_LEAVE;
557 return sz;
560 static ssize_t
561 convhdra(char const *str, size_t len, FILE *fp)
563 #ifdef HAVE_ICONV
564 struct str ciconv;
565 #endif
566 struct str cin;
567 ssize_t ret = 0;
568 NYD_ENTER;
570 cin.s = UNCONST(str);
571 cin.l = len;
572 #ifdef HAVE_ICONV
573 ciconv.s = NULL;
574 if (iconvd != (iconv_t)-1) {
575 ciconv.l = 0;
576 if (n_iconv_str(iconvd, &ciconv, &cin, NULL, FAL0) != 0)
577 goto jleave;
578 cin = ciconv;
580 #endif
581 ret = mime_write_tohdr(&cin, fp);
582 #ifdef HAVE_ICONV
583 jleave:
584 if (ciconv.s != NULL)
585 free(ciconv.s);
586 #endif
587 NYD_LEAVE;
588 return ret;
591 static ssize_t
592 mime_write_tohdr_a(struct str *in, FILE *f) /* TODO error handling */
594 char const *cp, *lastcp;
595 ssize_t sz, x;
596 NYD_ENTER;
598 in->s[in->l] = '\0';
599 lastcp = in->s;
600 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
601 if ((sz = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0)
602 goto jleave;
603 lastcp = cp;
604 } else {
605 cp = in->s;
606 sz = 0;
609 for ( ; *cp != '\0'; ++cp) {
610 switch (*cp) {
611 case '(':
612 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp + 1), f);
613 lastcp = ++cp;
614 cp = skip_comment(cp);
615 if (--cp > lastcp) {
616 if ((x = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0) {
617 sz = x;
618 goto jleave;
620 sz += x;
622 lastcp = cp;
623 break;
624 case '"':
625 while (*cp) {
626 if (*++cp == '"')
627 break;
628 if (*cp == '\\' && cp[1] != '\0')
629 ++cp;
631 break;
634 if (cp > lastcp)
635 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp), f);
636 jleave:
637 NYD_LEAVE;
638 return sz;
641 static void
642 addstr(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
644 NYD_ENTER;
645 *buf = srealloc(*buf, *sz += len);
646 memcpy(&(*buf)[*pos], str, len);
647 *pos += len;
648 NYD_LEAVE;
651 static void
652 addconv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
654 struct str in, out;
655 NYD_ENTER;
657 in.s = UNCONST(str);
658 in.l = len;
659 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
660 addstr(buf, sz, pos, out.s, out.l);
661 free(out.s);
662 NYD_LEAVE;
665 FL char const *
666 charset_get_7bit(void)
668 char const *t;
669 NYD_ENTER;
671 if ((t = ok_vlook(charset_7bit)) == NULL)
672 t = CHARSET_7BIT;
673 NYD_LEAVE;
674 return t;
677 #ifdef HAVE_ICONV
678 FL char const *
679 charset_get_8bit(void)
681 char const *t;
682 NYD_ENTER;
684 if ((t = ok_vlook(CHARSET_8BIT_OKEY)) == NULL)
685 t = CHARSET_8BIT;
686 NYD_LEAVE;
687 return t;
689 #endif
691 FL char const *
692 charset_get_lc(void)
694 char const *t;
695 NYD_ENTER;
697 if ((t = ok_vlook(ttycharset)) == NULL)
698 t = CHARSET_8BIT;
699 NYD_LEAVE;
700 return t;
703 FL bool_t
704 charset_iter_reset(char const *a_charset_to_try_first)
706 char const *sarr[3];
707 size_t sarrl[3], len;
708 char *cp;
709 NYD_ENTER;
710 UNUSED(a_charset_to_try_first);
712 #ifdef HAVE_ICONV
713 sarr[0] = a_charset_to_try_first;
714 if ((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
715 ok_blook(sendcharsets_else_ttycharset))
716 sarr[1] = charset_get_lc();
717 sarr[2] = charset_get_8bit();
718 #else
719 sarr[2] = charset_get_lc();
720 #endif
722 sarrl[2] = len = strlen(sarr[2]);
723 #ifdef HAVE_ICONV
724 if ((cp = UNCONST(sarr[1])) != NULL)
725 len += (sarrl[1] = strlen(cp));
726 else
727 sarrl[1] = 0;
728 if ((cp = UNCONST(sarr[0])) != NULL)
729 len += (sarrl[0] = strlen(cp));
730 else
731 sarrl[0] = 0;
732 #endif
734 _cs_iter_base = cp = salloc(len + 1 + 1 +1);
736 #ifdef HAVE_ICONV
737 if ((len = sarrl[0]) != 0) {
738 memcpy(cp, sarr[0], len);
739 cp[len] = ',';
740 cp += ++len;
742 if ((len = sarrl[1]) != 0) {
743 memcpy(cp, sarr[1], len);
744 cp[len] = ',';
745 cp += ++len;
747 #endif
748 len = sarrl[2];
749 memcpy(cp, sarr[2], len);
750 cp[len] = '\0';
752 _CS_ITER_STEP();
753 NYD_LEAVE;
754 return (_cs_iter != NULL);
757 FL bool_t
758 charset_iter_next(void)
760 bool_t rv;
761 NYD_ENTER;
763 _CS_ITER_STEP();
764 rv = (_cs_iter != NULL);
765 NYD_LEAVE;
766 return rv;
769 FL bool_t
770 charset_iter_is_valid(void)
772 bool_t rv;
773 NYD_ENTER;
775 rv = (_cs_iter != NULL);
776 NYD_LEAVE;
777 return rv;
780 FL char const *
781 charset_iter(void)
783 char const *rv;
784 NYD_ENTER;
786 rv = _cs_iter;
787 NYD_LEAVE;
788 return rv;
791 FL void
792 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
794 NYD_ENTER;
795 outer_storage[0] = _cs_iter_base;
796 outer_storage[1] = _cs_iter;
797 NYD_LEAVE;
800 FL void
801 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
803 NYD_ENTER;
804 _cs_iter_base = outer_storage[0];
805 _cs_iter = outer_storage[1];
806 NYD_LEAVE;
809 #ifdef HAVE_ICONV
810 FL char const *
811 need_hdrconv(struct header *hp, enum gfield w) /* TODO once only, then iter */
813 char const *ret = NULL;
814 NYD_ENTER;
816 if (w & GIDENT) {
817 if (hp->h_from != NULL) {
818 if (_name_highbit(hp->h_from))
819 goto jneeds;
820 } else if (_has_highbit(myaddrs(NULL)))
821 goto jneeds;
822 if (hp->h_organization) {
823 if (_has_highbit(hp->h_organization))
824 goto jneeds;
825 } else if (_has_highbit(ok_vlook(ORGANIZATION)))
826 goto jneeds;
827 if (hp->h_replyto) {
828 if (_name_highbit(hp->h_replyto))
829 goto jneeds;
830 } else if (_has_highbit(ok_vlook(replyto)))
831 goto jneeds;
832 if (hp->h_sender) {
833 if (_name_highbit(hp->h_sender))
834 goto jneeds;
835 } else if (_has_highbit(ok_vlook(sender)))
836 goto jneeds;
838 if ((w & GTO) && _name_highbit(hp->h_to))
839 goto jneeds;
840 if ((w & GCC) && _name_highbit(hp->h_cc))
841 goto jneeds;
842 if ((w & GBCC) && _name_highbit(hp->h_bcc))
843 goto jneeds;
844 if ((w & GSUBJECT) && _has_highbit(hp->h_subject))
845 jneeds:
846 ret = _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
847 NYD_LEAVE;
848 return ret;
850 #endif /* HAVE_ICONV */
852 FL enum mimeenc
853 mime_getenc(char *p)
855 enum mimeenc rv;
856 NYD_ENTER;
858 if (is_this_enc(p, "7bit"))
859 rv = MIME_7B;
860 else if (is_this_enc(p, "8bit"))
861 rv = MIME_8B;
862 else if (is_this_enc(p, "base64"))
863 rv = MIME_B64;
864 else if (is_this_enc(p, "binary"))
865 rv = MIME_BIN;
866 else if (is_this_enc(p, "quoted-printable"))
867 rv = MIME_QP;
868 else
869 rv = MIME_NONE;
870 NYD_LEAVE;
871 return rv;
874 FL char *
875 mime_getparam(char const *param, char *h)
877 char *p = h, *q, *rv = NULL;
878 int c;
879 size_t sz;
880 NYD_ENTER;
882 sz = strlen(param);
883 if (!whitechar(*p)) {
884 c = '\0';
885 while (*p && (*p != ';' || c == '\\')) {
886 c = (c == '\\') ? '\0' : *p;
887 ++p;
889 if (*p++ == '\0')
890 goto jleave;
893 for (;;) {
894 while (whitechar(*p))
895 ++p;
896 if (!ascncasecmp(p, param, sz)) {
897 p += sz;
898 while (whitechar(*p))
899 ++p;
900 if (*p++ == '=')
901 break;
903 c = '\0';
904 while (*p != '\0' && (*p != ';' || c == '\\')) {
905 if (*p == '"' && c != '\\') {
906 ++p;
907 while (*p != '\0' && (*p != '"' || c == '\\')) {
908 c = (c == '\\') ? '\0' : *p;
909 ++p;
911 ++p;
912 } else {
913 c = (c == '\\') ? '\0' : *p;
914 ++p;
917 if (*p++ == '\0')
918 goto jleave;
920 while (whitechar(*p))
921 ++p;
923 q = p;
924 if (*p == '"') {
925 p++;
926 if ((q = strchr(p, '"')) == NULL)
927 goto jleave;
928 } else {
929 while (*q != '\0' && !whitechar(*q) && *q != ';')
930 ++q;
932 sz = PTR2SIZE(q - p);
933 rv = salloc(q - p +1);
934 memcpy(rv, p, sz);
935 rv[sz] = '\0';
936 jleave:
937 NYD_LEAVE;
938 return rv;
941 FL char *
942 mime_get_boundary(char *h, size_t *len)
944 char *q = NULL, *p;
945 size_t sz;
946 NYD_ENTER;
948 if ((p = mime_getparam("boundary", h)) != NULL) {
949 sz = strlen(p);
950 if (len != NULL)
951 *len = sz + 2;
952 q = salloc(sz + 2 +1);
953 q[0] = q[1] = '-';
954 memcpy(q + 2, p, sz);
955 *(q + sz + 2) = '\0';
957 NYD_LEAVE;
958 return q;
961 FL char *
962 mime_create_boundary(void)
964 char *bp;
965 NYD_ENTER;
967 bp = salloc(48);
968 snprintf(bp, 48, "=_%011lu=-%s=_", (ul_it)time_current.tc_time,
969 getrandstring(47 - (11 + 6)));
970 NYD_LEAVE;
971 return bp;
974 FL int
975 mime_classify_file(FILE *fp, char const **contenttype, char const **charset,
976 int *do_iconv)
978 /* TODO classify once only PLEASE PLEASE PLEASE */
979 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
980 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
981 * TODO and report that state to the outer world */
982 #define F_ "From "
983 #define F_SIZEOF (sizeof(F_) -1)
985 char f_buf[F_SIZEOF], *f_p = f_buf;
986 enum {
987 _CLEAN = 0, /* Plain RFC 2822 message */
988 _NCTT = 1<<0, /* *contenttype == NULL */
989 _ISTXT = 1<<1, /* *contenttype =~ text/ */
990 _ISTXTCOK = 1<<2, /* _ISTXT + *mime-allow-text-controls* */
991 _HIGHBIT = 1<<3, /* Not 7bit clean */
992 _LONGLINES = 1<<4, /* MIME_LINELEN_LIMIT exceed. */
993 _CTRLCHAR = 1<<5, /* Control characters seen */
994 _HASNUL = 1<<6, /* Contains \0 characters */
995 _NOTERMNL = 1<<7, /* Lacks a final newline */
996 _TRAILWS = 1<<8, /* Blanks before NL */
997 _FROM_ = 1<<9 /* ^From_ seen */
998 } ctt = _CLEAN;
999 enum conversion convert;
1000 ssize_t curlen;
1001 int c, lastc;
1002 NYD_ENTER;
1004 assert(ftell(fp) == 0x0l);
1006 *do_iconv = 0;
1008 if (*contenttype == NULL)
1009 ctt = _NCTT;
1010 else if (!ascncasecmp(*contenttype, "text/", 5))
1011 ctt = ok_blook(mime_allow_text_controls) ? _ISTXT | _ISTXTCOK : _ISTXT;
1012 convert = _conversion_by_encoding();
1014 if (fsize(fp) == 0)
1015 goto j7bit;
1017 /* We have to inspect the file content */
1018 for (curlen = 0, c = EOF;; ++curlen) {
1019 lastc = c;
1020 c = getc(fp);
1022 if (c == '\0') {
1023 ctt |= _HASNUL;
1024 if (!(ctt & _ISTXTCOK))
1025 break;
1026 continue;
1028 if (c == '\n' || c == EOF) {
1029 if (curlen >= MIME_LINELEN_LIMIT)
1030 ctt |= _LONGLINES;
1031 if (c == EOF)
1032 break;
1033 if (blankchar(lastc))
1034 ctt |= _TRAILWS;
1035 f_p = f_buf;
1036 curlen = -1;
1037 continue;
1039 /* A bit hairy is handling of \r=\x0D=CR.
1040 * RFC 2045, 6.7:
1041 * Control characters other than TAB, or CR and LF as parts of CRLF
1042 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
1043 * we cannot peek the next character. Thus right here, inspect the last
1044 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
1045 /*else*/ if (lastc == '\r')
1046 ctt |= _CTRLCHAR;
1048 /* Control character? XXX this is all ASCII here */
1049 if (c < 0x20 || c == 0x7F) {
1050 /* RFC 2045, 6.7, as above ... */
1051 if (c != '\t' && c != '\r')
1052 ctt |= _CTRLCHAR;
1053 /* If there is a escape sequence in backslash notation defined for
1054 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
1055 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
1056 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
1057 * \e=\x1B=ESC */
1058 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
1059 continue;
1060 ctt |= _HASNUL; /* Force base64 */
1061 if (!(ctt & _ISTXTCOK))
1062 break;
1063 } else if ((ui8_t)c & 0x80) {
1064 ctt |= _HIGHBIT;
1065 /* TODO count chars with HIGHBIT? libmagic?
1066 * TODO try encode part - base64 if bails? */
1067 if (!(ctt & (_NCTT | _ISTXT))) { /* TODO _NCTT?? */
1068 ctt |= _HASNUL; /* Force base64 */
1069 break;
1071 } else if (!(ctt & _FROM_) && UICMP(z, curlen, <, F_SIZEOF)) {
1072 *f_p++ = (char)c;
1073 if (UICMP(z, curlen, ==, F_SIZEOF - 1) &&
1074 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
1075 !memcmp(f_buf, F_, F_SIZEOF))
1076 ctt |= _FROM_;
1079 if (lastc != '\n')
1080 ctt |= _NOTERMNL;
1081 rewind(fp);
1083 if (ctt & _HASNUL) {
1084 convert = CONV_TOB64;
1085 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1086 * on request; else enforce what file(1)/libmagic(3) would suggest */
1087 if (ctt & _ISTXTCOK)
1088 goto jcharset;
1089 if (ctt & (_NCTT | _ISTXT))
1090 *contenttype = "application/octet-stream";
1091 if (*charset == NULL)
1092 *charset = "binary";
1093 goto jleave;
1096 if (ctt & (_LONGLINES | _CTRLCHAR | _NOTERMNL | _TRAILWS | _FROM_)) {
1097 if (convert != CONV_TOB64)
1098 convert = CONV_TOQP;
1099 goto jstepi;
1101 if (ctt & _HIGHBIT) {
1102 jstepi:
1103 if (ctt & (_NCTT | _ISTXT))
1104 *do_iconv = ((ctt & _HIGHBIT) != 0);
1105 } else
1106 j7bit:
1107 convert = CONV_7BIT;
1108 if (ctt & _NCTT)
1109 *contenttype = "text/plain";
1111 /* Not an attachment with specified charset? */
1112 jcharset:
1113 if (*charset == NULL) /* TODO MIME/send: iter active? iter! else */
1114 *charset = (ctt & _HIGHBIT) ? _CS_ITER_GET() : charset_get_7bit();
1115 jleave:
1116 NYD_LEAVE;
1117 return convert;
1118 #undef F_
1119 #undef F_SIZEOF
1122 FL enum mimecontent
1123 mime_classify_content_of_part(struct mimepart const *mip)
1125 enum mimecontent mc;
1126 char const *ct;
1127 NYD_ENTER;
1129 mc = MIME_UNKNOWN;
1130 ct = mip->m_ct_type_plain;
1132 if (!asccasecmp(ct, "application/octet-stream") &&
1133 mip->m_filename != NULL && ok_blook(mime_counter_evidence)) {
1134 ct = mime_classify_content_type_by_fileext(mip->m_filename);
1135 if (ct == NULL)
1136 /* TODO how about let *mime-counter-evidence* have
1137 * TODO a value, and if set, saving the attachment in
1138 * TODO a temporary file that mime_classify_file() can
1139 * TODO examine, and using MIME_TEXT if that gives us
1140 * TODO something that seems to be human readable?! */
1141 goto jleave;
1143 if (strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
1144 mc = MIME_TEXT;
1145 else if (!asccasecmp(ct, "text/plain"))
1146 mc = MIME_TEXT_PLAIN;
1147 else if (!asccasecmp(ct, "text/html"))
1148 mc = MIME_TEXT_HTML;
1149 else if (!ascncasecmp(ct, "text/", 5))
1150 mc = MIME_TEXT;
1151 else if (!asccasecmp(ct, "message/rfc822"))
1152 mc = MIME_822;
1153 else if (!ascncasecmp(ct, "message/", 8))
1154 mc = MIME_MESSAGE;
1155 else if (!asccasecmp(ct, "multipart/alternative"))
1156 mc = MIME_ALTERNATIVE;
1157 else if (!asccasecmp(ct, "multipart/digest"))
1158 mc = MIME_DIGEST;
1159 else if (!ascncasecmp(ct, "multipart/", 10))
1160 mc = MIME_MULTI;
1161 else if (!asccasecmp(ct, "application/x-pkcs7-mime") ||
1162 !asccasecmp(ct, "application/pkcs7-mime"))
1163 mc = MIME_PKCS7;
1164 jleave:
1165 NYD_LEAVE;
1166 return mc;
1169 FL char *
1170 mime_classify_content_type_by_fileext(char const *name)
1172 char *content = NULL;
1173 struct mtnode *mtn;
1174 size_t nlen;
1175 NYD_ENTER;
1177 if ((name = strrchr(name, '.')) == NULL || *++name == '\0')
1178 goto jleave;
1180 if (_mt_list == NULL)
1181 _mt_init();
1182 if (NELEM(_mt_bltin) == 0 && _mt_list == (struct mtnode*)-1)
1183 goto jleave;
1185 nlen = strlen(name);
1186 for (mtn = _mt_list; mtn != NULL; mtn = mtn->mt_next) {
1187 char const *ext = mtn->mt_line + mtn->mt_mtlen + 1,
1188 *cp = ext;
1189 do {
1190 while (!whitechar(*cp) && *cp != '\0')
1191 ++cp;
1192 /* Better to do case-insensitive comparison on extension, since the
1193 * RFC doesn't specify case of attribute values? */
1194 if (nlen == PTR2SIZE(cp - ext) && !ascncasecmp(name, ext, nlen)) {
1195 content = savestrbuf(mtn->mt_line, mtn->mt_mtlen);
1196 goto jleave;
1198 while (whitechar(*cp) && *cp != '\0')
1199 ++cp;
1200 ext = cp;
1201 } while (*ext != '\0');
1203 jleave:
1204 NYD_LEAVE;
1205 return content;
1208 FL int
1209 c_mimetypes(void *v)
1211 char **argv = v;
1212 struct mtnode *mtn;
1213 NYD_ENTER;
1215 if (*argv == NULL)
1216 goto jlist;
1217 if (argv[1] != NULL)
1218 goto jerr;
1219 if (!asccasecmp(*argv, "show"))
1220 goto jlist;
1221 if (!asccasecmp(*argv, "clear"))
1222 goto jclear;
1223 jerr:
1224 fprintf(stderr, "Synopsis: mimetypes: %s\n",
1225 tr(418, "Either <show> (default) or <clear> the mime.types cache"));
1226 v = NULL;
1227 jleave:
1228 NYD_LEAVE;
1229 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1231 jlist: {
1232 FILE *fp;
1233 size_t l;
1235 if (_mt_list == NULL)
1236 _mt_init();
1237 if (NELEM(_mt_bltin) == 0 && _mt_list == (struct mtnode*)-1) {
1238 fprintf(stderr, tr(57, "Interpolate what file?\n"));
1239 v = NULL;
1240 goto jleave;
1243 if ((fp = Ftmp(NULL, "mimelist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
1244 NULL) {
1245 perror("tmpfile");
1246 v = NULL;
1247 goto jleave;
1250 for (l = 0, mtn = _mt_list; mtn != NULL; ++l, mtn = mtn->mt_next)
1251 fprintf(fp, "%s\t%s\n", mtn->mt_line, mtn->mt_line + mtn->mt_mtlen + 1);
1253 page_or_print(fp, l);
1254 Fclose(fp);
1256 goto jleave;
1258 jclear:
1259 if (NELEM(_mt_bltin) == 0 && _mt_list == (struct mtnode*)-1)
1260 _mt_list = NULL;
1261 while ((mtn = _mt_list) != NULL) {
1262 _mt_list = mtn->mt_next;
1263 free(mtn);
1265 goto jleave;
1268 FL void
1269 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
1271 /* TODO mime_fromhdr(): is called with strings that contain newlines;
1272 * TODO this is the usual newline problem all around the codebase;
1273 * TODO i.e., if we strip it, then the display misses it ;>
1274 * TODO this is why it is so messy and why S-nail v14.2 plus additional
1275 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
1276 * TODO why our display reflects what is contained in the message: the 1:1
1277 * TODO relationship of message content and display!
1278 * TODO instead a header line should be decoded to what it is (a single
1279 * TODO line that is) and it should be objective to the backend wether
1280 * TODO it'll be folded to fit onto the display or not, e.g., for search
1281 * TODO purposes etc. then the only condition we have to honour in here
1282 * TODO is that whitespace in between multiple adjacent MIME encoded words
1283 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
1284 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
1285 struct str cin, cout;
1286 char *p, *op, *upper, *cs, *cbeg;
1287 ui32_t convert, lastenc, lastoutl;
1288 #ifdef HAVE_ICONV
1289 char const *tcs;
1290 iconv_t fhicd = (iconv_t)-1;
1291 #endif
1292 NYD_ENTER;
1294 out->l = 0;
1295 if (in->l == 0) {
1296 *(out->s = smalloc(1)) = '\0';
1297 goto jleave;
1299 out->s = NULL;
1301 #ifdef HAVE_ICONV
1302 tcs = charset_get_lc();
1303 #endif
1304 p = in->s;
1305 upper = p + in->l;
1306 lastenc = lastoutl = 0;
1308 while (p < upper) {
1309 op = p;
1310 if (*p == '=' && *(p + 1) == '?') {
1311 p += 2;
1312 cbeg = p;
1313 while (p < upper && *p != '?')
1314 ++p; /* strip charset */
1315 if (p >= upper)
1316 goto jnotmime;
1317 cs = salloc(PTR2SIZE(++p - cbeg));
1318 memcpy(cs, cbeg, PTR2SIZE(p - cbeg - 1));
1319 cs[p - cbeg - 1] = '\0';
1320 #ifdef HAVE_ICONV
1321 if (fhicd != (iconv_t)-1)
1322 n_iconv_close(fhicd);
1323 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
1324 #endif
1325 switch (*p) {
1326 case 'B': case 'b':
1327 convert = CONV_FROMB64;
1328 break;
1329 case 'Q': case 'q':
1330 convert = CONV_FROMQP;
1331 break;
1332 default: /* invalid, ignore */
1333 goto jnotmime;
1335 if (*++p != '?')
1336 goto jnotmime;
1337 cin.s = ++p;
1338 cin.l = 1;
1339 for (;;) {
1340 if (PTRCMP(p + 1, >=, upper))
1341 goto jnotmime;
1342 if (*p++ == '?' && *p == '=')
1343 break;
1344 ++cin.l;
1346 ++p;
1347 --cin.l;
1349 cout.s = NULL;
1350 cout.l = 0;
1351 if (convert == CONV_FROMB64) {
1352 /* XXX Take care for, and strip LF from
1353 * XXX [Invalid Base64 encoding ignored] */
1354 if (b64_decode(&cout, &cin, NULL) == STOP &&
1355 cout.s[cout.l - 1] == '\n')
1356 --cout.l;
1357 } else
1358 qp_decode(&cout, &cin, NULL);
1360 out->l = lastenc;
1361 #ifdef HAVE_ICONV
1362 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
1363 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
1364 convert = n_iconv_str(fhicd, &cin, &cout, NULL, TRU1);
1365 out = n_str_add(out, &cin);
1366 if (convert) /* EINVAL at EOS */
1367 out = n_str_add_buf(out, "?", 1);
1368 free(cin.s);
1369 } else
1370 #endif
1371 out = n_str_add(out, &cout);
1372 lastenc = lastoutl = out->l;
1373 free(cout.s);
1374 } else
1375 jnotmime: {
1376 bool_t onlyws;
1378 p = op;
1379 onlyws = (lastenc > 0);
1380 for (;;) {
1381 if (++op == upper)
1382 break;
1383 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
1384 break;
1385 if (onlyws && !blankchar(*op))
1386 onlyws = FAL0;
1389 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
1390 p = op;
1391 if (!onlyws || lastoutl != lastenc)
1392 lastenc = out->l;
1393 lastoutl = out->l;
1396 out->s[out->l] = '\0';
1398 if (flags & TD_ISPR) {
1399 makeprint(out, &cout);
1400 free(out->s);
1401 *out = cout;
1403 if (flags & TD_DELCTRL)
1404 out->l = delctrl(out->s, out->l);
1405 #ifdef HAVE_ICONV
1406 if (fhicd != (iconv_t)-1)
1407 n_iconv_close(fhicd);
1408 #endif
1409 jleave:
1410 NYD_LEAVE;
1411 return;
1414 FL char *
1415 mime_fromaddr(char const *name)
1417 char const *cp, *lastcp;
1418 char *res = NULL;
1419 size_t ressz = 1, rescur = 0;
1420 NYD_ENTER;
1422 if (name == NULL)
1423 goto jleave;
1424 if (*name == '\0') {
1425 res = savestr(name);
1426 goto jleave;
1429 if ((cp = routeaddr(name)) != NULL && cp > name) {
1430 addconv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
1431 lastcp = cp;
1432 } else
1433 cp = lastcp = name;
1435 for ( ; *cp; ++cp) {
1436 switch (*cp) {
1437 case '(':
1438 addstr(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
1439 lastcp = ++cp;
1440 cp = skip_comment(cp);
1441 if (--cp > lastcp)
1442 addconv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1443 lastcp = cp;
1444 break;
1445 case '"':
1446 while (*cp) {
1447 if (*++cp == '"')
1448 break;
1449 if (*cp == '\\' && cp[1] != '\0')
1450 ++cp;
1452 break;
1455 if (cp > lastcp)
1456 addstr(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1457 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1458 if (rescur == 0)
1459 res = UNCONST("");
1460 else
1461 res[rescur] = '\0';
1462 { char *x = res;
1463 res = savestr(res);
1464 free(x);
1466 jleave:
1467 NYD_LEAVE;
1468 return res;
1471 FL ssize_t
1472 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1473 enum tdflags dflags, struct str *rest)
1475 ssize_t rv;
1476 struct quoteflt *qf;
1477 NYD_ENTER;
1479 quoteflt_reset(qf = quoteflt_dummy(), f);
1480 rv = mime_write(ptr, size, f, convert, dflags, qf, rest);
1481 assert(quoteflt_flush(qf) == 0);
1482 NYD_LEAVE;
1483 return rv;
1486 FL ssize_t
1487 mime_write(char const *ptr, size_t size, FILE *f,
1488 enum conversion convert, enum tdflags dflags,
1489 struct quoteflt *qf, struct str *rest)
1491 /* TODO note: after send/MIME layer rewrite we will have a string pool
1492 * TODO so that memory allocation count drops down massively; for now,
1493 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1494 struct str in, out;
1495 ssize_t sz;
1496 int state;
1497 NYD_ENTER;
1499 in.s = UNCONST(ptr);
1500 in.l = size;
1501 out.s = NULL;
1502 out.l = 0;
1504 dflags |= _TD_BUFCOPY;
1505 if ((sz = size) == 0) {
1506 if (rest != NULL && rest->l != 0)
1507 goto jconvert;
1508 goto jleave;
1511 #ifdef HAVE_ICONV
1512 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1513 (convert == CONV_TOQP || convert == CONV_8BIT ||
1514 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1515 if (n_iconv_str(iconvd, &out, &in, NULL, FAL0) != 0) {
1516 /* XXX report conversion error? */;
1517 sz = -1;
1518 goto jleave;
1520 in = out;
1521 out.s = NULL;
1522 dflags &= ~_TD_BUFCOPY;
1524 #endif
1526 jconvert:
1527 switch (convert) {
1528 case CONV_FROMQP:
1529 state = qp_decode(&out, &in, rest);
1530 goto jqpb64_dec;
1531 case CONV_TOQP:
1532 qp_encode(&out, &in, QP_NONE);
1533 goto jqpb64_enc;
1534 case CONV_8BIT:
1535 sz = quoteflt_push(qf, in.s, in.l);
1536 break;
1537 case CONV_FROMB64:
1538 rest = NULL;
1539 /* FALLTHRU */
1540 case CONV_FROMB64_T:
1541 state = b64_decode(&out, &in, rest);
1542 jqpb64_dec:
1543 if ((sz = out.l) != 0) {
1544 ui32_t opl = qf->qf_pfix_len;
1545 if (state != OKAY)
1546 qf->qf_pfix_len = 0;
1547 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), rest, qf);
1548 qf->qf_pfix_len = opl;
1550 if (state != OKAY)
1551 sz = -1;
1552 break;
1553 case CONV_TOB64:
1554 b64_encode(&out, &in, B64_LF | B64_MULTILINE);
1555 jqpb64_enc:
1556 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1557 if (sz != (ssize_t)out.l)
1558 sz = -1;
1559 break;
1560 case CONV_FROMHDR:
1561 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1562 sz = quoteflt_push(qf, out.s, out.l);
1563 break;
1564 case CONV_TOHDR:
1565 sz = mime_write_tohdr(&in, f);
1566 break;
1567 case CONV_TOHDR_A:
1568 sz = mime_write_tohdr_a(&in, f);
1569 break;
1570 default:
1571 sz = _fwrite_td(&in, dflags, NULL, qf);
1572 break;
1574 jleave:
1575 if (out.s != NULL)
1576 free(out.s);
1577 if (in.s != ptr)
1578 free(in.s);
1579 NYD_LEAVE;
1580 return sz;
1583 /* vim:set fenc=utf-8:s-it-mode */