nail.h: struct message.m_spamscore: ui_it -> ui32_t
[s-mailx.git] / mime.c
blob2567619469b33c1f54f0daa930c597aae6971c9d
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 #define _CHARSET() ((_cs_iter != NULL) ? _cs_iter : charset_get_8bit())
46 struct mtnode {
47 struct mtnode *mt_next;
48 size_t mt_mtlen; /* Length of MIME type string */
49 char mt_line[VFIELD_SIZE(8)];
52 static char const * const _mt_sources[] = {
53 /* XXX Order fixed due to *mimetypes-load-control* handling! */
54 MIME_TYPES_USR, MIME_TYPES_SYS, NULL
56 * const _mt_bltin[] = {
57 #include "mime_types.h"
58 NULL
61 struct mtnode *_mt_list;
62 char *_cs_iter_base, *_cs_iter;
64 /* Initialize MIME type list */
65 static void _mt_init(void);
66 static void __mt_add_line(char const *line, struct mtnode **tail);
68 /* Is 7-bit enough? */
69 static bool_t _has_highbit(char const *s);
70 static bool_t _name_highbit(struct name *np);
72 /* Get the conversion that matches *encoding* */
73 static enum conversion _conversion_by_encoding(void);
75 /* fwrite(3) while checking for displayability */
76 static ssize_t _fwrite_td(struct str const *input, enum tdflags flags,
77 struct str *rest, struct quoteflt *qf);
79 static size_t delctrl(char *cp, size_t sz);
81 static int is_this_enc(char const *line, char const *encoding);
83 /* Convert header fields to RFC 1522 format and write to the file fo */
84 static size_t mime_write_tohdr(struct str *in, FILE *fo);
86 /* Write len characters of the passed string to the passed file, doing charset
87 * and header conversion */
88 static size_t convhdra(char const *str, size_t len, FILE *fp);
90 /* Write an address to a header field */
91 static size_t mime_write_tohdr_a(struct str *in, FILE *f);
93 /* Append to buf, handling resizing */
94 static void addstr(char **buf, size_t *sz, size_t *pos,
95 char const *str, size_t len);
97 static void addconv(char **buf, size_t *sz, size_t *pos,
98 char const *str, size_t len);
100 static void
101 _mt_init(void)
103 struct mtnode *tail = NULL;
104 char *line = NULL;
105 size_t linesize = 0;
106 ui32_t idx, idx_ok;
107 char const *ccp, * const *srcs;
108 FILE *fp;
109 NYD_ENTER;
111 if ((ccp = ok_vlook(mimetypes_load_control)) == NULL)
112 idx_ok = (ui32_t)-1;
113 else for (idx_ok = 0; *ccp != '\0'; ++ccp)
114 switch (*ccp) {
115 case 'S':
116 case 's':
117 idx_ok |= 1 << 1;
118 break;
119 case 'U':
120 case 'u':
121 idx_ok |= 1 << 0;
122 break;
123 default:
124 /* XXX bad *mimetypes-load-control*; log error? */
125 break;
128 for (idx = 1, srcs = _mt_sources; *srcs != NULL; idx <<= 1, ++srcs) {
129 if ((idx & idx_ok) == 0 || (ccp = file_expand(*srcs)) == NULL)
130 continue;
131 if ((fp = Fopen(ccp, "r")) == NULL) {
132 /*fprintf(stderr, tr(176, "Cannot open %s\n"), fn);*/
133 continue;
135 while (fgetline(&line, &linesize, NULL, NULL, fp, 0))
136 __mt_add_line(line, &tail);
137 Fclose(fp);
139 if (line != NULL)
140 free(line);
142 for (srcs = _mt_bltin; *srcs != NULL; ++srcs)
143 __mt_add_line(*srcs, &tail);
144 NYD_LEAVE;
147 static void
148 __mt_add_line(char const *line, struct mtnode **tail) /* XXX diag? dups!*/
150 char const *typ;
151 size_t tlen, elen;
152 struct mtnode *mtn;
153 NYD_ENTER;
155 if (!alphachar(*line))
156 goto jleave;
158 typ = line;
159 while (blankchar(*line) == 0 && *line != '\0')
160 ++line;
161 if (*line == '\0')
162 goto jleave;
163 tlen = PTR2SIZE(line - typ);
165 while (blankchar(*line) != 0 && *line != '\0')
166 ++line;
167 if (*line == '\0')
168 goto jleave;
170 elen = strlen(line);
171 if (line[elen - 1] == '\n' && line[--elen] == '\0')
172 goto jleave;
174 mtn = smalloc(sizeof(struct mtnode) -
175 VFIELD_SIZEOF(struct mtnode, mt_line) + tlen + 1 + elen +1);
176 if (*tail != NULL)
177 (*tail)->mt_next = mtn;
178 else
179 _mt_list = mtn;
180 *tail = mtn;
181 mtn->mt_next = NULL;
182 mtn->mt_mtlen = tlen;
183 memcpy(mtn->mt_line, typ, tlen);
184 mtn->mt_line[tlen] = '\0';
185 ++tlen;
186 memcpy(mtn->mt_line + tlen, line, elen);
187 tlen += elen;
188 mtn->mt_line[tlen] = '\0';
189 jleave:
190 NYD_LEAVE;
193 static bool_t
194 _has_highbit(char const *s)
196 bool_t rv = TRU1;
197 NYD_ENTER;
199 if (s) {
201 if (*s & 0200)
202 goto jleave;
203 while (*s++ != '\0');
205 rv = FAL0;
206 jleave:
207 NYD_LEAVE;
208 return rv;
211 static bool_t
212 _name_highbit(struct name *np)
214 bool_t rv = TRU1;
215 NYD_ENTER;
217 while (np) {
218 if (_has_highbit(np->n_name) || _has_highbit(np->n_fullname))
219 goto jleave;
220 np = np->n_flink;
222 rv = FAL0;
223 jleave:
224 NYD_LEAVE;
225 return rv;
228 static enum conversion
229 _conversion_by_encoding(void)
231 char const *cp;
232 enum conversion ret;
233 NYD_ENTER;
235 if ((cp = ok_vlook(encoding)) == NULL)
236 ret = MIME_DEFAULT_ENCODING;
237 else if (!strcmp(cp, "quoted-printable"))
238 ret = CONV_TOQP;
239 else if (!strcmp(cp, "8bit"))
240 ret = CONV_8BIT;
241 else if (!strcmp(cp, "base64"))
242 ret = CONV_TOB64;
243 else {
244 fprintf(stderr, tr(177, "Warning: invalid encoding %s, using base64\n"),
245 cp);
246 ret = CONV_TOB64;
248 NYD_LEAVE;
249 return ret;
252 static ssize_t
253 _fwrite_td(struct str const *input, enum tdflags flags, struct str *rest,
254 struct quoteflt *qf)
256 /* TODO note: after send/MIME layer rewrite we will have a string pool
257 * TODO so that memory allocation count drops down massively; for now,
258 * TODO v14.* that is, we pay a lot & heavily depend on the allocator */
259 /* TODO well if we get a broken pipe here, and it happens to
260 * TODO happen pretty easy when sleeping in a full pipe buffer,
261 * TODO then the current codebase performs longjump away;
262 * TODO this leaves memory leaks behind ('think up to 3 per,
263 * TODO dep. upon alloca availability). For this to be fixed
264 * TODO we either need to get rid of the longjmp()s (tm) or
265 * TODO the storage must come from the outside or be tracked
266 * TODO in a carrier struct. Best both. But storage reuse
267 * TODO would be a bigbig win besides */
268 /* *input* _may_ point to non-modifyable buffer; but even then it only
269 * needs to be dup'ed away if we have to transform the content */
270 struct str in, out;
271 ssize_t rv;
272 NYD_ENTER;
273 UNUSED(rest);
275 in = *input;
276 out.s = NULL;
277 out.l = 0;
279 #ifdef HAVE_ICONV
280 if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
281 char *buf = NULL;
283 if (rest != NULL && rest->l > 0) {
284 in.l = rest->l + input->l;
285 in.s = buf = smalloc(in.l + 1);
286 memcpy(in.s, rest->s, rest->l);
287 memcpy(in.s + rest->l, input->s, input->l);
288 rest->l = 0;
291 if (n_iconv_str(iconvd, &out, &in, &in, TRU1) != 0 && rest != NULL &&
292 in.l > 0) {
293 /* Incomplete multibyte at EOF is special */
294 if (flags & _TD_EOF) {
295 out.s = srealloc(out.s, out.l + 4);
296 /* TODO 0xFFFD out.s[out.l++] = '[';*/
297 out.s[out.l++] = '?'; /* TODO 0xFFFD !!! */
298 /* TODO 0xFFFD out.s[out.l++] = ']';*/
299 } else
300 n_str_add(rest, &in);
302 in = out;
303 out.s = NULL;
304 flags &= ~_TD_BUFCOPY;
306 if (buf != NULL)
307 free(buf);
309 #endif
311 if (flags & TD_ISPR)
312 makeprint(&in, &out);
313 else if (flags & _TD_BUFCOPY)
314 n_str_dup(&out, &in);
315 else
316 out = in;
317 if (flags & TD_DELCTRL)
318 out.l = delctrl(out.s, out.l);
320 rv = quoteflt_push(qf, out.s, out.l);
322 if (out.s != in.s)
323 free(out.s);
324 if (in.s != input->s)
325 free(in.s);
326 NYD_LEAVE;
327 return rv;
330 static size_t
331 delctrl(char *cp, size_t sz)
333 size_t x = 0, y = 0;
334 NYD_ENTER;
336 while (x < sz) {
337 if (!cntrlchar(cp[x]))
338 cp[y++] = cp[x];
339 ++x;
341 NYD_LEAVE;
342 return y;
345 static int
346 is_this_enc(char const *line, char const *encoding)
348 int rv, c, quoted = 0;
349 NYD_ENTER;
351 if (*line == '"')
352 quoted = 1, line++;
353 rv = 0;
354 while (*line && *encoding)
355 if ((c = *line++, lowerconv(c) != *encoding++))
356 goto jleave;
357 rv = 1;
358 if (quoted && *line == '"')
359 goto jleave;
360 if (*line == '\0' || whitechar(*line))
361 goto jleave;
362 rv = 0;
363 jleave:
364 NYD_LEAVE;
365 return rv;
368 static size_t
369 mime_write_tohdr(struct str *in, FILE *fo) /* TODO rewrite - FAST! */
371 struct str cin, cout;
372 char buf[B64_LINESIZE +1]; /* (No CR/LF used) */
373 char const *charset7, *charset, *upper, *wbeg, *wend, *lastspc,
374 *lastwordend = NULL;
375 size_t sz = 0, col = 0, quoteany, wr, charsetlen,
376 maxcol = 65 /* there is the header field's name, too */;
377 bool_t highbit, mustquote, broken;
378 NYD_ENTER;
380 charset7 = charset_get_7bit();
381 charset = _CHARSET();
382 wr = strlen(charset7);
383 charsetlen = strlen(charset);
384 charsetlen = MAX(charsetlen, wr);
385 upper = in->s + in->l;
387 /* xxx note this results in too much hits since =/? force quoting even
388 * xxx if they don't form =? etc. */
389 quoteany = mime_cte_mustquote(in->s, in->l, TRU1);
391 highbit = FAL0;
392 if (quoteany != 0)
393 for (wbeg = in->s; wbeg < upper; ++wbeg)
394 if ((ui8_t)*wbeg & 0x80)
395 highbit = TRU1;
397 if (quoteany << 1 > in->l) {
398 /* Print the entire field in base64 */
399 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
400 wend = upper;
401 cin.s = UNCONST(wbeg);
402 for (;;) {
403 cin.l = wend - wbeg;
404 if (cin.l * 4/3 + 7 + charsetlen < maxcol - col) {
405 cout.s = buf;
406 cout.l = sizeof buf;
407 wr = fprintf(fo, "=?%s?B?%s?=", (highbit ? charset : charset7),
408 b64_encode(&cout, &cin, B64_BUF)->s);
409 sz += wr;
410 col += wr;
411 if (wend < upper) {
412 fwrite("\n ", sizeof(char), 2, fo);
413 sz += 2;
414 col = 0;
415 maxcol = 76;
417 break;
418 } else {
419 if (col) {
420 fprintf(fo, "\n ");
421 sz += 2;
422 col = 0;
423 maxcol = 76;
424 } else
425 wend -= 4;
429 } else {
430 /* Print the field word-wise in quoted-printable */
431 broken = FAL0;
432 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
433 lastspc = NULL;
434 while (wbeg < upper && whitechar(*wbeg)) {
435 lastspc = lastspc ? lastspc : wbeg;
436 ++wbeg;
437 ++col;
438 broken = FAL0;
440 if (wbeg == upper) {
441 if (lastspc)
442 while (lastspc < wbeg) {
443 putc(*lastspc&0377, fo);
444 ++lastspc;
445 ++sz;
447 break;
450 if (lastspc != NULL)
451 broken = FAL0;
452 highbit = FAL0;
453 for (wend = wbeg; wend < upper && !whitechar(*wend); ++wend)
454 if ((ui8_t)*wend & 0x80)
455 highbit = TRU1;
456 mustquote = (mime_cte_mustquote(wbeg, PTR2SIZE(wend - wbeg), TRU1)
457 != 0);
459 if (mustquote || broken || ((wend - wbeg) >= 76-5 && quoteany)) {
460 for (cout.s = NULL;;) {
461 cin.s = UNCONST(lastwordend ? lastwordend : wbeg);
462 cin.l = wend - cin.s;
463 qp_encode(&cout, &cin, QP_ISHEAD);
464 wr = cout.l + charsetlen + 7;
465 jqp_retest:
466 if (col <= maxcol && wr <= maxcol - col) {
467 if (lastspc) {
468 /* TODO because we included the WS in the encoded str,
469 * TODO put SP only??
470 * TODO RFC: "any 'linear-white-space' that separates
471 * TODO a pair of adjacent 'encoded-word's is ignored" */
472 putc(' ', fo);
473 ++sz;
474 ++col;
476 fprintf(fo, "=?%s?Q?%.*s?=",
477 (highbit ? charset : charset7), (int)cout.l, cout.s);
478 sz += wr;
479 col += wr;
480 break;
481 } else if (col > 1) {
482 /* TODO assuming SP separator, ignore *lastspc* !?? */
483 broken = TRU1;
484 if (lastspc != NULL) {
485 putc('\n', fo);
486 ++sz;
487 col = 0;
488 } else {
489 fputs("\n ", fo);
490 sz += 2;
491 col = 1;
493 maxcol = 76;
494 goto jqp_retest;
495 } else {
496 for (;;) { /* XXX */
497 wend -= 4;
498 assert(wend > wbeg);
499 if (wr - 4 < maxcol)
500 break;
501 wr -= 4;
505 if (cout.s != NULL)
506 free(cout.s);
507 lastwordend = wend;
508 } else {
509 if (col && PTR2SIZE(wend - wbeg) > maxcol - col) {
510 putc('\n', fo);
511 ++sz;
512 col = 0;
513 maxcol = 76;
514 if (lastspc == NULL) {
515 putc(' ', fo);
516 ++sz;
517 --maxcol;
518 } else
519 maxcol -= wbeg - lastspc;
521 if (lastspc)
522 while (lastspc < wbeg) {
523 putc(*lastspc&0377, fo);
524 ++lastspc;
525 ++sz;
527 wr = fwrite(wbeg, sizeof *wbeg, PTR2SIZE(wend - wbeg), fo);
528 sz += wr;
529 col += wr;
530 lastwordend = NULL;
534 NYD_LEAVE;
535 return sz;
538 static size_t
539 convhdra(char const *str, size_t len, FILE *fp)
541 #ifdef HAVE_ICONV
542 struct str ciconv;
543 #endif
544 struct str cin;
545 size_t ret = 0;
546 NYD_ENTER;
548 cin.s = UNCONST(str);
549 cin.l = len;
550 #ifdef HAVE_ICONV
551 ciconv.s = NULL;
552 if (iconvd != (iconv_t)-1) {
553 ciconv.l = 0;
554 if (n_iconv_str(iconvd, &ciconv, &cin, NULL, FAL0) != 0)
555 goto jleave;
556 cin = ciconv;
558 #endif
559 ret = mime_write_tohdr(&cin, fp);
560 #ifdef HAVE_ICONV
561 jleave:
562 if (ciconv.s != NULL)
563 free(ciconv.s);
564 #endif
565 NYD_LEAVE;
566 return ret;
569 static size_t
570 mime_write_tohdr_a(struct str *in, FILE *f)
572 char const *cp, *lastcp;
573 size_t sz = 0;
574 NYD_ENTER;
576 in->s[in->l] = '\0';
577 lastcp = in->s;
578 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
579 sz += convhdra(lastcp, cp - lastcp, f);
580 lastcp = cp;
581 } else
582 cp = in->s;
583 for ( ; *cp != '\0'; ++cp) {
584 switch (*cp) {
585 case '(':
586 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp + 1), f);
587 lastcp = ++cp;
588 cp = skip_comment(cp);
589 if (--cp > lastcp)
590 sz += convhdra(lastcp, PTR2SIZE(cp - lastcp), f);
591 lastcp = cp;
592 break;
593 case '"':
594 while (*cp) {
595 if (*++cp == '"')
596 break;
597 if (*cp == '\\' && cp[1] != '\0')
598 cp++;
600 break;
603 if (cp > lastcp)
604 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp), f);
605 NYD_LEAVE;
606 return sz;
609 static void
610 addstr(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
612 NYD_ENTER;
613 *buf = srealloc(*buf, *sz += len);
614 memcpy(&(*buf)[*pos], str, len);
615 *pos += len;
616 NYD_LEAVE;
619 static void
620 addconv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
622 struct str in, out;
623 NYD_ENTER;
625 in.s = UNCONST(str);
626 in.l = len;
627 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
628 addstr(buf, sz, pos, out.s, out.l);
629 free(out.s);
630 NYD_LEAVE;
633 FL char const *
634 charset_get_7bit(void)
636 char const *t;
637 NYD_ENTER;
639 if ((t = ok_vlook(charset_7bit)) == NULL)
640 t = CHARSET_7BIT;
641 NYD_LEAVE;
642 return t;
645 FL char const *
646 charset_get_8bit(void)
648 char const *t;
649 NYD_ENTER;
651 if ((t = ok_vlook(CHARSET_8BIT_OKEY)) == NULL)
652 t = CHARSET_8BIT;
653 NYD_LEAVE;
654 return t;
657 FL char const *
658 charset_get_lc(void)
660 char const *t;
661 NYD_ENTER;
663 if ((t = ok_vlook(ttycharset)) == NULL)
664 t = CHARSET_8BIT;
665 NYD_LEAVE;
666 return t;
669 FL void
670 charset_iter_reset(char const *a_charset_to_try_first)
672 char const *sarr[3];
673 size_t sarrl[3], len;
674 char *cp;
675 NYD_ENTER;
677 sarr[0] = a_charset_to_try_first;
678 #ifdef HAVE_ICONV
679 if ((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
680 ok_blook(sendcharsets_else_ttycharset))
681 sarr[1] = charset_get_lc();
682 #endif
683 sarr[2] = charset_get_8bit();
685 sarrl[2] = len = strlen(sarr[2]);
686 #ifdef HAVE_ICONV
687 if ((cp = UNCONST(sarr[1])) != NULL)
688 len += (sarrl[1] = strlen(cp));
689 else
690 sarrl[1] = 0;
691 if ((cp = UNCONST(sarr[0])) != NULL)
692 len += (sarrl[0] = strlen(cp));
693 else
694 sarrl[0] = 0;
695 #endif
697 _cs_iter_base = cp = salloc(len + 1 + 1 +1);
699 #ifdef HAVE_ICONV
700 if ((len = sarrl[0]) != 0) {
701 memcpy(cp, sarr[0], len);
702 cp[len] = ',';
703 cp += ++len;
705 if ((len = sarrl[1]) != 0) {
706 memcpy(cp, sarr[1], len);
707 cp[len] = ',';
708 cp += ++len;
710 #endif
711 len = sarrl[2];
712 memcpy(cp, sarr[2], len);
713 cp[len] = '\0';
714 _cs_iter = NULL;
715 NYD_LEAVE;
718 FL char const *
719 charset_iter_next(void)
721 char const *rv;
722 NYD_ENTER;
724 rv = _cs_iter = strcomma(&_cs_iter_base, 1);
725 NYD_LEAVE;
726 return rv;
729 FL char const *
730 charset_iter_current(void)
732 char const *rv;
733 NYD_ENTER;
735 rv = _cs_iter;
736 NYD_LEAVE;
737 return rv;
740 FL void
741 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
743 NYD_ENTER;
744 outer_storage[0] = _cs_iter_base;
745 outer_storage[1] = _cs_iter;
746 NYD_LEAVE;
749 FL void
750 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
752 NYD_ENTER;
753 _cs_iter_base = outer_storage[0];
754 _cs_iter = outer_storage[1];
755 NYD_LEAVE;
758 FL char const *
759 need_hdrconv(struct header *hp, enum gfield w)
761 char const *ret = NULL;
762 NYD_ENTER;
764 if (w & GIDENT) {
765 if (hp->h_from != NULL) {
766 if (_name_highbit(hp->h_from))
767 goto jneeds;
768 } else if (_has_highbit(myaddrs(NULL)))
769 goto jneeds;
770 if (hp->h_organization) {
771 if (_has_highbit(hp->h_organization))
772 goto jneeds;
773 } else if (_has_highbit(ok_vlook(ORGANIZATION)))
774 goto jneeds;
775 if (hp->h_replyto) {
776 if (_name_highbit(hp->h_replyto))
777 goto jneeds;
778 } else if (_has_highbit(ok_vlook(replyto)))
779 goto jneeds;
780 if (hp->h_sender) {
781 if (_name_highbit(hp->h_sender))
782 goto jneeds;
783 } else if (_has_highbit(ok_vlook(sender)))
784 goto jneeds;
786 if (w & GTO && _name_highbit(hp->h_to))
787 goto jneeds;
788 if (w & GCC && _name_highbit(hp->h_cc))
789 goto jneeds;
790 if (w & GBCC && _name_highbit(hp->h_bcc))
791 goto jneeds;
792 if (w & GSUBJECT && _has_highbit(hp->h_subject))
793 jneeds:
794 ret = _CHARSET();
795 NYD_LEAVE;
796 return ret;
799 FL enum mimeenc
800 mime_getenc(char *p)
802 enum mimeenc rv;
803 NYD_ENTER;
805 if (is_this_enc(p, "7bit"))
806 rv = MIME_7B;
807 else if (is_this_enc(p, "8bit"))
808 rv = MIME_8B;
809 else if (is_this_enc(p, "base64"))
810 rv = MIME_B64;
811 else if (is_this_enc(p, "binary"))
812 rv = MIME_BIN;
813 else if (is_this_enc(p, "quoted-printable"))
814 rv = MIME_QP;
815 else
816 rv = MIME_NONE;
817 NYD_LEAVE;
818 return rv;
821 FL char *
822 mime_getparam(char const *param, char *h)
824 char *p = h, *q, *rv = NULL;
825 int c;
826 size_t sz;
827 NYD_ENTER;
829 sz = strlen(param);
830 if (!whitechar(*p)) {
831 c = '\0';
832 while (*p && (*p != ';' || c == '\\')) {
833 c = (c == '\\') ? '\0' : *p;
834 ++p;
836 if (*p++ == '\0')
837 goto jleave;
840 for (;;) {
841 while (whitechar(*p))
842 ++p;
843 if (ascncasecmp(p, param, sz) == 0) {
844 p += sz;
845 while (whitechar(*p))
846 ++p;
847 if (*p++ == '=')
848 break;
850 c = '\0';
851 while (*p && (*p != ';' || c == '\\')) {
852 if (*p == '"' && c != '\\') {
853 ++p;
854 while (*p && (*p != '"' || c == '\\')) {
855 c = (c == '\\') ? '\0' : *p;
856 ++p;
858 ++p;
859 } else {
860 c = (c == '\\') ? '\0' : *p;
861 ++p;
864 if (*p++ == '\0')
865 goto jleave;
867 while (whitechar(*p))
868 ++p;
870 q = p;
871 if (*p == '"') {
872 p++;
873 if ((q = strchr(p, '"')) == NULL)
874 goto jleave;
875 } else {
876 while (*q && !whitechar(*q) && *q != ';')
877 ++q;
879 sz = q - p;
880 rv = salloc(q - p +1);
881 memcpy(rv, p, sz);
882 rv[sz] = '\0';
883 jleave:
884 NYD_LEAVE;
885 return rv;
888 FL char *
889 mime_get_boundary(char *h, size_t *len)
891 char *q = NULL, *p;
892 size_t sz;
893 NYD_ENTER;
895 if ((p = mime_getparam("boundary", h)) != NULL) {
896 sz = strlen(p);
897 if (len != NULL)
898 *len = sz + 2;
899 q = salloc(sz + 2 +1);
900 q[0] = q[1] = '-';
901 memcpy(q + 2, p, sz);
902 *(q + sz + 2) = '\0';
904 NYD_LEAVE;
905 return q;
908 FL char *
909 mime_create_boundary(void)
911 char *bp;
912 NYD_ENTER;
914 bp = salloc(48);
915 snprintf(bp, 48, "=_%011lu=-%s=_", (ul_it)time_current.tc_time,
916 getrandstring(47 - (11 + 6)));
917 NYD_LEAVE;
918 return bp;
921 FL int
922 mime_classify_file(FILE *fp, char const **contenttype, char const **charset,
923 int *do_iconv)
925 /* TODO classify once only PLEASE PLEASE PLEASE */
926 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
927 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
928 * TODO and report that state to the outer world */
929 #define F_ "From "
930 #define F_SIZEOF (sizeof(F_) - 1)
932 char f_buf[F_SIZEOF], *f_p = f_buf;
933 enum {
934 _CLEAN = 0, /* Plain RFC 2822 message */
935 _NCTT = 1<<0, /* *contenttype == NULL */
936 _ISTXT = 1<<1, /* *contenttype =~ text/ */
937 _ISTXTCOK = 1<<2, /* _ISTXT+*mime-allow-text-controls* */
938 _HIGHBIT = 1<<3, /* Not 7bit clean */
939 _LONGLINES = 1<<4, /* MIME_LINELEN_LIMIT exceed. */
940 _CTRLCHAR = 1<<5, /* Control characters seen */
941 _HASNUL = 1<<6, /* Contains \0 characters */
942 _NOTERMNL = 1<<7, /* Lacks a final newline */
943 _TRAILWS = 1<<8, /* Blanks before NL */
944 _FROM_ = 1<<9 /* ^From_ seen */
945 } ctt = _CLEAN;
946 enum conversion convert;
947 sl_it curlen;
948 int c, lastc;
949 NYD_ENTER;
951 assert(ftell(fp) == 0x0l);
953 *do_iconv = 0;
955 if (*contenttype == NULL)
956 ctt = _NCTT;
957 else if (ascncasecmp(*contenttype, "text/", 5) == 0)
958 ctt = ok_blook(mime_allow_text_controls) ? _ISTXT | _ISTXTCOK : _ISTXT;
959 convert = _conversion_by_encoding();
961 if (fsize(fp) == 0)
962 goto j7bit;
964 /* We have to inspect the file content */
965 for (curlen = 0, c = EOF;; ++curlen) {
966 lastc = c;
967 c = getc(fp);
969 if (c == '\0') {
970 ctt |= _HASNUL;
971 if ((ctt & _ISTXTCOK) == 0)
972 break;
973 continue;
975 if (c == '\n' || c == EOF) {
976 if (curlen >= MIME_LINELEN_LIMIT)
977 ctt |= _LONGLINES;
978 if (c == EOF)
979 break;
980 if (blankchar(lastc))
981 ctt |= _TRAILWS;
982 f_p = f_buf;
983 curlen = -1;
984 continue;
986 /* A bit hairy is handling of \r=\x0D=CR.
987 * RFC 2045, 6.7:
988 * Control characters other than TAB, or CR and LF as parts of CRLF
989 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
990 * we cannot peek the next character. Thus right here, inspect the last
991 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
992 /*else*/ if (lastc == '\r')
993 ctt |= _CTRLCHAR;
995 /* Control character? */
996 if (c < 0x20 || c == 0x7F) {
997 /* RFC 2045, 6.7, as above ... */
998 if (c != '\t' && c != '\r')
999 ctt |= _CTRLCHAR;
1000 /* If there is a escape sequence in backslash notation defined for
1001 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
1002 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
1003 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
1004 * \e=\x1B=ESC */
1005 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
1006 continue;
1007 ctt |= _HASNUL; /* Force base64 */
1008 if ((ctt & _ISTXTCOK) == 0)
1009 break;
1010 } else if (c & 0x80) {
1011 ctt |= _HIGHBIT;
1012 /* TODO count chars with HIGHBIT? libmagic?
1013 * TODO try encode part - base64 if bails? */
1014 if ((ctt & (_NCTT|_ISTXT)) == 0) { /* TODO _NCTT?? */
1015 ctt |= _HASNUL; /* Force base64 */
1016 break;
1018 } else if ((ctt & _FROM_) == 0 && curlen < (sl_it)F_SIZEOF) {
1019 *f_p++ = (char)c;
1020 if (curlen == (sl_it)(F_SIZEOF - 1) &&
1021 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
1022 memcmp(f_buf, F_, F_SIZEOF) == 0)
1023 ctt |= _FROM_;
1026 if (lastc != '\n')
1027 ctt |= _NOTERMNL;
1028 rewind(fp);
1030 if (ctt & _HASNUL) {
1031 convert = CONV_TOB64;
1032 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1033 * on request; else enforce what file(1)/libmagic(3) would suggest */
1034 if (ctt & _ISTXTCOK)
1035 goto jcharset;
1036 if (ctt & (_NCTT | _ISTXT))
1037 *contenttype = "application/octet-stream";
1038 if (*charset == NULL)
1039 *charset = "binary";
1040 goto jleave;
1043 if (ctt & (_LONGLINES | _CTRLCHAR | _NOTERMNL | _TRAILWS | _FROM_)) {
1044 convert = CONV_TOQP;
1045 goto jstepi;
1047 if (ctt & _HIGHBIT) {
1048 jstepi:
1049 if (ctt & (_NCTT|_ISTXT))
1050 *do_iconv = (ctt & _HIGHBIT) != 0;
1051 } else
1052 j7bit:
1053 convert = CONV_7BIT;
1054 if (ctt & _NCTT)
1055 *contenttype = "text/plain";
1057 /* Not an attachment with specified charset? */
1058 jcharset:
1059 if (*charset == NULL)
1060 *charset = (ctt & _HIGHBIT) ? _CHARSET() : charset_get_7bit();
1061 jleave:
1062 NYD_LEAVE;
1063 return convert;
1064 #undef F_
1065 #undef F_SIZEOF
1068 FL enum mimecontent
1069 mime_classify_content_of_part(struct mimepart const *mip)
1071 enum mimecontent mc = MIME_UNKNOWN;
1072 char const *ct = mip->m_ct_type_plain;
1073 NYD_ENTER;
1075 if (asccasecmp(ct, "application/octet-stream") == 0 &&
1076 mip->m_filename != NULL && ok_blook(mime_counter_evidence)) {
1077 ct = mime_classify_content_type_by_fileext(mip->m_filename);
1078 if (ct == NULL)
1079 /* TODO how about let *mime-counter-evidence* have
1080 * TODO a value, and if set, saving the attachment in
1081 * TODO a temporary file that mime_classify_file() can
1082 * TODO examine, and using MIME_TEXT if that gives us
1083 * TODO something that seems to be human readable?! */
1084 goto jleave;
1086 if (strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
1087 mc = MIME_TEXT;
1088 else if (asccasecmp(ct, "text/plain") == 0)
1089 mc = MIME_TEXT_PLAIN;
1090 else if (asccasecmp(ct, "text/html") == 0)
1091 mc = MIME_TEXT_HTML;
1092 else if (ascncasecmp(ct, "text/", 5) == 0)
1093 mc = MIME_TEXT;
1094 else if (asccasecmp(ct, "message/rfc822") == 0)
1095 mc = MIME_822;
1096 else if (ascncasecmp(ct, "message/", 8) == 0)
1097 mc = MIME_MESSAGE;
1098 else if (asccasecmp(ct, "multipart/alternative") == 0)
1099 mc = MIME_ALTERNATIVE;
1100 else if (asccasecmp(ct, "multipart/digest") == 0)
1101 mc = MIME_DIGEST;
1102 else if (ascncasecmp(ct, "multipart/", 10) == 0)
1103 mc = MIME_MULTI;
1104 else if (asccasecmp(ct, "application/x-pkcs7-mime") == 0 ||
1105 asccasecmp(ct, "application/pkcs7-mime") == 0)
1106 mc = MIME_PKCS7;
1107 jleave:
1108 NYD_LEAVE;
1109 return mc;
1112 FL char *
1113 mime_classify_content_type_by_fileext(char const *name)
1115 char *content = NULL;
1116 struct mtnode *mtn;
1117 size_t nlen;
1118 NYD_ENTER;
1120 if ((name = strrchr(name, '.')) == NULL || *++name == '\0')
1121 goto jleave;
1123 if (_mt_list == NULL)
1124 _mt_init();
1126 nlen = strlen(name);
1127 for (mtn = _mt_list; mtn != NULL; mtn = mtn->mt_next) {
1128 char const *ext = mtn->mt_line + mtn->mt_mtlen + 1,
1129 *cp = ext;
1130 do {
1131 while (!whitechar(*cp) && *cp != '\0')
1132 ++cp;
1133 /* Better to do case-insensitive comparison on extension, since the
1134 * RFC doesn't specify case of attribute values? */
1135 if (nlen == PTR2SIZE(cp - ext) && ascncasecmp(name, ext, nlen) == 0) {
1136 content = savestrbuf(mtn->mt_line, mtn->mt_mtlen);
1137 goto jleave;
1139 while (whitechar(*cp) && *cp != '\0')
1140 ++cp;
1141 ext = cp;
1142 } while (*ext != '\0');
1144 jleave:
1145 NYD_LEAVE;
1146 return content;
1149 FL int
1150 c_mimetypes(void *v)
1152 char **argv = v;
1153 struct mtnode *mtn;
1154 NYD_ENTER;
1156 if (*argv == NULL)
1157 goto jlist;
1158 if (argv[1] != NULL)
1159 goto jerr;
1160 if (asccasecmp(*argv, "show") == 0)
1161 goto jlist;
1162 if (asccasecmp(*argv, "clear") == 0)
1163 goto jclear;
1164 jerr:
1165 fprintf(stderr, "Synopsis: mimetypes: %s\n",
1166 tr(418, "Either <show> (default) or <clear> the mime.types cache"));
1167 v = NULL;
1168 jleave:
1169 NYD_LEAVE;
1170 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1172 jlist: {
1173 FILE *fp;
1174 size_t l;
1176 if (_mt_list == NULL)
1177 _mt_init();
1179 if ((fp = Ftmp(NULL, "mimelist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
1180 NULL) {
1181 perror("tmpfile");
1182 v = NULL;
1183 goto jleave;
1186 for (l = 0, mtn = _mt_list; mtn != NULL; ++l, mtn = mtn->mt_next)
1187 fprintf(fp, "%s\t%s\n", mtn->mt_line, mtn->mt_line + mtn->mt_mtlen + 1);
1189 page_or_print(fp, l);
1190 Fclose(fp);
1192 goto jleave;
1194 jclear:
1195 while ((mtn = _mt_list) != NULL) {
1196 _mt_list = mtn->mt_next;
1197 free(mtn);
1199 goto jleave;
1202 FL void
1203 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
1205 /* TODO mime_fromhdr(): is called with strings that contain newlines;
1206 * TODO this is the usual newline problem all around the codebase;
1207 * TODO i.e., if we strip it, then the display misses it ;>
1208 * TODO this is why it is so messy and why S-nail v14.2 plus additional
1209 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
1210 * TODO why our display reflects what is contained in the message: the 1:1
1211 * TODO relationship of message content and display!
1212 * TODO instead a header line should be decoded to what it is (a single
1213 * TODO line that is) and it should be objective to the backend wether
1214 * TODO it'll be folded to fit onto the display or not, e.g., for search
1215 * TODO purposes etc. then the only condition we have to honour in here
1216 * TODO is that whitespace in between multiple adjacent MIME encoded words
1217 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
1218 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
1219 struct str cin, cout;
1220 char *p, *op, *upper, *cs, *cbeg;
1221 ui32_t convert, lastenc, lastoutl;
1222 #ifdef HAVE_ICONV
1223 char const *tcs;
1224 iconv_t fhicd = (iconv_t)-1;
1225 #endif
1226 NYD_ENTER;
1228 out->l = 0;
1229 if (in->l == 0) {
1230 *(out->s = smalloc(1)) = '\0';
1231 goto jleave;
1233 out->s = NULL;
1235 #ifdef HAVE_ICONV
1236 tcs = charset_get_lc();
1237 #endif
1238 p = in->s;
1239 upper = p + in->l;
1240 lastenc = lastoutl = 0;
1242 while (p < upper) {
1243 op = p;
1244 if (*p == '=' && *(p + 1) == '?') {
1245 p += 2;
1246 cbeg = p;
1247 while (p < upper && *p != '?')
1248 ++p; /* strip charset */
1249 if (p >= upper)
1250 goto jnotmime;
1251 cs = salloc(PTR2SIZE(++p - cbeg));
1252 memcpy(cs, cbeg, PTR2SIZE(p - cbeg - 1));
1253 cs[p - cbeg - 1] = '\0';
1254 #ifdef HAVE_ICONV
1255 if (fhicd != (iconv_t)-1)
1256 n_iconv_close(fhicd);
1257 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
1258 #endif
1259 switch (*p) {
1260 case 'B': case 'b':
1261 convert = CONV_FROMB64;
1262 break;
1263 case 'Q': case 'q':
1264 convert = CONV_FROMQP;
1265 break;
1266 default: /* invalid, ignore */
1267 goto jnotmime;
1269 if (*++p != '?')
1270 goto jnotmime;
1271 cin.s = ++p;
1272 cin.l = 1;
1273 for (;;) {
1274 if (PTRCMP(p + 1, >=, upper))
1275 goto jnotmime;
1276 if (*p++ == '?' && *p == '=')
1277 break;
1278 ++cin.l;
1280 ++p;
1281 --cin.l;
1283 cout.s = NULL;
1284 cout.l = 0;
1285 if (convert == CONV_FROMB64) {
1286 /* XXX Take care for, and strip LF from
1287 * XXX [Invalid Base64 encoding ignored] */
1288 if (b64_decode(&cout, &cin, NULL) == STOP &&
1289 cout.s[cout.l - 1] == '\n')
1290 --cout.l;
1291 } else
1292 qp_decode(&cout, &cin, NULL);
1294 out->l = lastenc;
1295 #ifdef HAVE_ICONV
1296 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
1297 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
1298 convert = n_iconv_str(fhicd, &cin, &cout, NULL, TRU1);
1299 out = n_str_add(out, &cin);
1300 if (convert) /* EINVAL at EOS */
1301 out = n_str_add_buf(out, "?", 1);
1302 free(cin.s);
1303 } else
1304 #endif
1305 out = n_str_add(out, &cout);
1306 lastenc = lastoutl = out->l;
1307 free(cout.s);
1308 } else
1309 jnotmime: {
1310 bool_t onlyws;
1312 p = op;
1313 onlyws = (lastenc > 0);
1314 for (;;) {
1315 if (++op == upper)
1316 break;
1317 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
1318 break;
1319 if (onlyws && !blankchar(*op))
1320 onlyws = FAL0;
1323 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
1324 p = op;
1325 if (!onlyws || lastoutl != lastenc)
1326 lastenc = out->l;
1327 lastoutl = out->l;
1330 out->s[out->l] = '\0';
1332 if (flags & TD_ISPR) {
1333 makeprint(out, &cout);
1334 free(out->s);
1335 *out = cout;
1337 if (flags & TD_DELCTRL)
1338 out->l = delctrl(out->s, out->l);
1339 #ifdef HAVE_ICONV
1340 if (fhicd != (iconv_t)-1)
1341 n_iconv_close(fhicd);
1342 #endif
1343 jleave:
1344 NYD_LEAVE;
1345 return;
1348 FL char *
1349 mime_fromaddr(char const *name)
1351 char const *cp, *lastcp;
1352 char *res = NULL;
1353 size_t ressz = 1, rescur = 0;
1354 NYD_ENTER;
1356 if (name == NULL)
1357 goto jleave;
1358 if (*name == '\0') {
1359 res = savestr(name);
1360 goto jleave;
1363 if ((cp = routeaddr(name)) != NULL && cp > name) {
1364 addconv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
1365 lastcp = cp;
1366 } else
1367 cp = lastcp = name;
1369 for ( ; *cp; ++cp) {
1370 switch (*cp) {
1371 case '(':
1372 addstr(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
1373 lastcp = ++cp;
1374 cp = skip_comment(cp);
1375 if (--cp > lastcp)
1376 addconv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1377 lastcp = cp;
1378 break;
1379 case '"':
1380 while (*cp) {
1381 if (*++cp == '"')
1382 break;
1383 if (*cp == '\\' && cp[1] != '\0')
1384 ++cp;
1386 break;
1389 if (cp > lastcp)
1390 addstr(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1391 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1392 if (rescur == 0)
1393 res = UNCONST("");
1394 else
1395 res[rescur] = '\0';
1396 { char *x = res;
1397 res = savestr(res);
1398 free(x);
1400 jleave:
1401 NYD_LEAVE;
1402 return res;
1405 FL ssize_t
1406 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1407 enum tdflags dflags, struct str *rest)
1409 ssize_t rv;
1410 struct quoteflt *qf;
1411 NYD_ENTER;
1413 quoteflt_reset(qf = quoteflt_dummy(), f);
1414 rv = mime_write(ptr, size, f, convert, dflags, qf, rest);
1415 assert(quoteflt_flush(qf) == 0);
1416 NYD_LEAVE;
1417 return rv;
1420 FL ssize_t
1421 mime_write(char const *ptr, size_t size, FILE *f,
1422 enum conversion convert, enum tdflags dflags,
1423 struct quoteflt *qf, struct str *rest)
1425 /* TODO note: after send/MIME layer rewrite we will have a string pool
1426 * TODO so that memory allocation count drops down massively; for now,
1427 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1428 struct str in, out;
1429 ssize_t sz;
1430 int state;
1431 NYD_ENTER;
1433 in.s = UNCONST(ptr);
1434 in.l = size;
1435 out.s = NULL;
1436 out.l = 0;
1438 dflags |= _TD_BUFCOPY;
1439 if ((sz = size) == 0) {
1440 if (rest != NULL && rest->l != 0)
1441 goto jconvert;
1442 goto jleave;
1445 #ifdef HAVE_ICONV
1446 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1447 (convert == CONV_TOQP || convert == CONV_8BIT ||
1448 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1449 if (n_iconv_str(iconvd, &out, &in, NULL, FAL0) != 0) {
1450 /* XXX report conversion error? */;
1451 sz = -1;
1452 goto jleave;
1454 in = out;
1455 out.s = NULL;
1456 dflags &= ~_TD_BUFCOPY;
1458 #endif
1460 jconvert:
1461 switch (convert) {
1462 case CONV_FROMQP:
1463 state = qp_decode(&out, &in, rest);
1464 goto jqpb64_dec;
1465 case CONV_TOQP:
1466 qp_encode(&out, &in, QP_NONE);
1467 goto jqpb64_enc;
1468 case CONV_8BIT:
1469 sz = quoteflt_push(qf, in.s, in.l);
1470 break;
1471 case CONV_FROMB64:
1472 rest = NULL;
1473 /* FALLTHRU */
1474 case CONV_FROMB64_T:
1475 state = b64_decode(&out, &in, rest);
1476 jqpb64_dec:
1477 if ((sz = out.l) != 0) {
1478 ui32_t opl = qf->qf_pfix_len;
1479 if (state != OKAY)
1480 qf->qf_pfix_len = 0;
1481 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), rest,qf);
1482 qf->qf_pfix_len = opl;
1484 if (state != OKAY)
1485 sz = -1;
1486 break;
1487 case CONV_TOB64:
1488 b64_encode(&out, &in, B64_LF | B64_MULTILINE);
1489 jqpb64_enc:
1490 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1491 if (sz != (ssize_t)out.l)
1492 sz = -1;
1493 break;
1494 case CONV_FROMHDR:
1495 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1496 sz = quoteflt_push(qf, out.s, out.l);
1497 break;
1498 case CONV_TOHDR:
1499 sz = mime_write_tohdr(&in, f);
1500 break;
1501 case CONV_TOHDR_A:
1502 sz = mime_write_tohdr_a(&in, f);
1503 break;
1504 default:
1505 sz = _fwrite_td(&in, dflags, NULL, qf);
1506 break;
1508 jleave:
1509 if (out.s != NULL)
1510 free(out.s);
1511 if (in.s != ptr)
1512 free(in.s);
1513 NYD_LEAVE;
1514 return sz;
1517 /* vim:set fenc=utf-8:s-it-mode */