NEWS: update to correct false STD I/O statement
[s-mailx.git] / mime.c
blobc236cbc4f5b211ea4c3c6b1121efffa78affeecb
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 #ifdef HAVE_ICONV
70 static bool_t _has_highbit(char const *s);
71 static bool_t _name_highbit(struct name *np);
72 #endif
74 /* Get the conversion that matches *encoding* */
75 static enum conversion _conversion_by_encoding(void);
77 /* fwrite(3) while checking for displayability */
78 static ssize_t _fwrite_td(struct str const *input, enum tdflags flags,
79 struct str *rest, struct quoteflt *qf);
81 static size_t delctrl(char *cp, size_t sz);
83 static int is_this_enc(char const *line, char const *encoding);
85 /* Convert header fields to RFC 1522 format and write to the file fo */
86 static size_t mime_write_tohdr(struct str *in, FILE *fo);
88 /* Write len characters of the passed string to the passed file, doing charset
89 * and header conversion */
90 static size_t convhdra(char const *str, size_t len, FILE *fp);
92 /* Write an address to a header field */
93 static size_t mime_write_tohdr_a(struct str *in, FILE *f);
95 /* Append to buf, handling resizing */
96 static void addstr(char **buf, size_t *sz, size_t *pos,
97 char const *str, size_t len);
99 static void addconv(char **buf, size_t *sz, size_t *pos,
100 char const *str, size_t len);
102 static void
103 _mt_init(void)
105 struct mtnode *tail = NULL;
106 char *line = NULL; /* TODO line pool */
107 size_t linesize = 0;
108 ui32_t idx, idx_ok;
109 char const *ccp, * const *srcs;
110 FILE *fp;
111 NYD_ENTER;
113 if ((ccp = ok_vlook(mimetypes_load_control)) == NULL)
114 idx_ok = (ui32_t)-1;
115 else for (idx_ok = 0; *ccp != '\0'; ++ccp)
116 switch (*ccp) {
117 case 'S':
118 case 's':
119 idx_ok |= 1 << 1;
120 break;
121 case 'U':
122 case 'u':
123 idx_ok |= 1 << 0;
124 break;
125 default:
126 /* XXX bad *mimetypes-load-control*; log error? */
127 break;
130 for (idx = 1, srcs = _mt_sources; *srcs != NULL; idx <<= 1, ++srcs) {
131 if (!(idx & idx_ok) || (ccp = file_expand(*srcs)) == NULL)
132 continue;
133 if ((fp = Fopen(ccp, "r")) == NULL) {
134 /*fprintf(stderr, tr(176, "Cannot open %s\n"), fn);*/
135 continue;
137 while (fgetline(&line, &linesize, NULL, NULL, fp, 0))
138 __mt_add_line(line, &tail);
139 Fclose(fp);
141 if (line != NULL)
142 free(line);
144 for (srcs = _mt_bltin; *srcs != NULL; ++srcs)
145 __mt_add_line(*srcs, &tail);
146 NYD_LEAVE;
149 static void
150 __mt_add_line(char const *line, struct mtnode **tail) /* XXX diag? dups!*/
152 char const *typ;
153 size_t tlen, elen;
154 struct mtnode *mtn;
155 NYD_ENTER;
157 if (!alphachar(*line))
158 goto jleave;
160 typ = line;
161 while (!blankchar(*line) && *line != '\0')
162 ++line;
163 if (*line == '\0')
164 goto jleave;
165 tlen = PTR2SIZE(line - typ);
167 while (blankchar(*line) && *line != '\0')
168 ++line;
170 if (*line == '\0')
171 goto jleave;
172 elen = strlen(line);
173 if (line[elen - 1] == '\n') {
174 if (--elen > 0 && line[elen - 1] == '\r')
175 --elen;
176 if (elen == 0)
177 goto jleave;
180 mtn = smalloc(sizeof(struct mtnode) -
181 VFIELD_SIZEOF(struct mtnode, mt_line) + tlen + 1 + elen +1);
182 if (*tail != NULL)
183 (*tail)->mt_next = mtn;
184 else
185 _mt_list = mtn;
186 *tail = mtn;
187 mtn->mt_next = NULL;
188 mtn->mt_mtlen = tlen;
189 memcpy(mtn->mt_line, typ, tlen);
190 mtn->mt_line[tlen] = '\0';
191 ++tlen;
192 memcpy(mtn->mt_line + tlen, line, elen);
193 tlen += elen;
194 mtn->mt_line[tlen] = '\0';
195 jleave:
196 NYD_LEAVE;
199 #ifdef HAVE_ICONV
200 static bool_t
201 _has_highbit(char const *s)
203 bool_t rv = TRU1;
204 NYD_ENTER;
206 if (s) {
208 if ((ui8_t)*s & 0x80)
209 goto jleave;
210 while (*s++ != '\0');
212 rv = FAL0;
213 jleave:
214 NYD_LEAVE;
215 return rv;
218 static bool_t
219 _name_highbit(struct name *np)
221 bool_t rv = TRU1;
222 NYD_ENTER;
224 while (np) {
225 if (_has_highbit(np->n_name) || _has_highbit(np->n_fullname))
226 goto jleave;
227 np = np->n_flink;
229 rv = FAL0;
230 jleave:
231 NYD_LEAVE;
232 return rv;
234 #endif /* HAVE_ICONV */
236 static enum conversion
237 _conversion_by_encoding(void)
239 char const *cp;
240 enum conversion ret;
241 NYD_ENTER;
243 if ((cp = ok_vlook(encoding)) == NULL)
244 ret = MIME_DEFAULT_ENCODING;
245 else if (!strcmp(cp, "quoted-printable"))
246 ret = CONV_TOQP;
247 else if (!strcmp(cp, "8bit"))
248 ret = CONV_8BIT;
249 else if (!strcmp(cp, "base64"))
250 ret = CONV_TOB64;
251 else {
252 fprintf(stderr, tr(177, "Warning: invalid encoding %s, using base64\n"),
253 cp);
254 ret = CONV_TOB64;
256 NYD_LEAVE;
257 return ret;
260 static ssize_t
261 _fwrite_td(struct str const *input, enum tdflags flags, struct str *rest,
262 struct quoteflt *qf)
264 /* TODO note: after send/MIME layer rewrite we will have a string pool
265 * TODO so that memory allocation count drops down massively; for now,
266 * TODO v14.* that is, we pay a lot & heavily depend on the allocator */
267 /* TODO well if we get a broken pipe here, and it happens to
268 * TODO happen pretty easy when sleeping in a full pipe buffer,
269 * TODO then the current codebase performs longjump away;
270 * TODO this leaves memory leaks behind ('think up to 3 per,
271 * TODO dep. upon alloca availability). For this to be fixed
272 * TODO we either need to get rid of the longjmp()s (tm) or
273 * TODO the storage must come from the outside or be tracked
274 * TODO in a carrier struct. Best both. But storage reuse
275 * TODO would be a bigbig win besides */
276 /* *input* _may_ point to non-modifyable buffer; but even then it only
277 * needs to be dup'ed away if we have to transform the content */
278 struct str in, out;
279 ssize_t rv;
280 NYD_ENTER;
281 UNUSED(rest);
283 in = *input;
284 out.s = NULL;
285 out.l = 0;
287 #ifdef HAVE_ICONV
288 if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
289 char *buf = NULL;
291 if (rest != NULL && rest->l > 0) {
292 in.l = rest->l + input->l;
293 in.s = buf = smalloc(in.l +1);
294 memcpy(in.s, rest->s, rest->l);
295 memcpy(in.s + rest->l, input->s, input->l);
296 rest->l = 0;
299 if (n_iconv_str(iconvd, &out, &in, &in, TRU1) != 0 && rest != NULL &&
300 in.l > 0) {
301 /* Incomplete multibyte at EOF is special */
302 if (flags & _TD_EOF) {
303 out.s = srealloc(out.s, out.l + 4);
304 /* TODO 0xFFFD out.s[out.l++] = '[';*/
305 out.s[out.l++] = '?'; /* TODO 0xFFFD !!! */
306 /* TODO 0xFFFD out.s[out.l++] = ']';*/
307 } else
308 n_str_add(rest, &in);
310 in = out;
311 out.s = NULL;
312 flags &= ~_TD_BUFCOPY;
314 if (buf != NULL)
315 free(buf);
317 #endif
319 if (flags & TD_ISPR)
320 makeprint(&in, &out);
321 else if (flags & _TD_BUFCOPY)
322 n_str_dup(&out, &in);
323 else
324 out = in;
325 if (flags & TD_DELCTRL)
326 out.l = delctrl(out.s, out.l);
328 rv = quoteflt_push(qf, out.s, out.l);
330 if (out.s != in.s)
331 free(out.s);
332 if (in.s != input->s)
333 free(in.s);
334 NYD_LEAVE;
335 return rv;
338 static size_t
339 delctrl(char *cp, size_t sz)
341 size_t x = 0, y = 0;
342 NYD_ENTER;
344 while (x < sz) {
345 if (!cntrlchar(cp[x]))
346 cp[y++] = cp[x];
347 ++x;
349 NYD_LEAVE;
350 return y;
353 static int
354 is_this_enc(char const *line, char const *encoding)
356 int rv, c, quoted = 0;
357 NYD_ENTER;
359 if (*line == '"')
360 quoted = 1, ++line;
361 rv = 0;
362 while (*line != '\0' && *encoding)
363 if ((c = *line++, lowerconv(c) != *encoding++))
364 goto jleave;
365 rv = 1;
366 if (quoted && *line == '"')
367 goto jleave;
368 if (*line == '\0' || whitechar(*line))
369 goto jleave;
370 rv = 0;
371 jleave:
372 NYD_LEAVE;
373 return rv;
376 static size_t
377 mime_write_tohdr(struct str *in, FILE *fo) /* TODO rewrite - FAST! */
379 struct str cin, cout;
380 char buf[B64_LINESIZE +1]; /* (No CR/LF used) */
381 char const *charset7, *charset, *upper, *wbeg, *wend, *lastspc,
382 *lastwordend = NULL;
383 size_t sz = 0, col = 0, quoteany, wr, charsetlen,
384 maxcol = 65 /* there is the header field's name, too */;
385 bool_t highbit, mustquote, broken;
386 NYD_ENTER;
388 charset7 = charset_get_7bit();
389 charset = _CHARSET();
390 wr = strlen(charset7);
391 charsetlen = strlen(charset);
392 charsetlen = MAX(charsetlen, wr);
393 upper = in->s + in->l;
395 /* xxx note this results in too much hits since =/? force quoting even
396 * xxx if they don't form =? etc. */
397 quoteany = mime_cte_mustquote(in->s, in->l, TRU1);
399 highbit = FAL0;
400 if (quoteany != 0)
401 for (wbeg = in->s; wbeg < upper; ++wbeg)
402 if ((ui8_t)*wbeg & 0x80)
403 highbit = TRU1;
405 if (quoteany << 1 > in->l) {
406 /* Print the entire field in base64 */
407 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
408 wend = upper;
409 cin.s = UNCONST(wbeg);
410 for (;;) {
411 cin.l = PTR2SIZE(wend - wbeg);
412 if (cin.l * 4/3 + 7 + charsetlen < maxcol - col) {
413 cout.s = buf;
414 cout.l = sizeof buf;
415 wr = fprintf(fo, "=?%s?B?%s?=", (highbit ? charset : charset7),
416 b64_encode(&cout, &cin, B64_BUF)->s);
417 sz += wr;
418 col += wr;
419 if (wend < upper) {
420 fwrite("\n ", sizeof(char), 2, fo);
421 sz += 2;
422 col = 0;
423 maxcol = 76;
425 break;
426 } else {
427 if (col) {
428 fprintf(fo, "\n ");
429 sz += 2;
430 col = 0;
431 maxcol = 76;
432 } else
433 wend -= 4;
437 } else {
438 /* Print the field word-wise in quoted-printable */
439 broken = FAL0;
440 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
441 lastspc = NULL;
442 while (wbeg < upper && whitechar(*wbeg)) {
443 lastspc = lastspc ? lastspc : wbeg;
444 ++wbeg;
445 ++col;
446 broken = FAL0;
448 if (wbeg == upper) {
449 if (lastspc)
450 while (lastspc < wbeg) {
451 putc(*lastspc&0377, fo);
452 ++lastspc;
453 ++sz;
455 break;
458 if (lastspc != NULL)
459 broken = FAL0;
460 highbit = FAL0;
461 for (wend = wbeg; wend < upper && !whitechar(*wend); ++wend)
462 if ((ui8_t)*wend & 0x80)
463 highbit = TRU1;
464 mustquote = (mime_cte_mustquote(wbeg, PTR2SIZE(wend - wbeg), TRU1)
465 != 0);
467 if (mustquote || broken ||
468 (PTR2SIZE(wend - wbeg) >= 76-5 && quoteany)) {
469 for (cout.s = NULL;;) {
470 cin.s = UNCONST(lastwordend ? lastwordend : wbeg);
471 cin.l = PTR2SIZE(wend - cin.s);
472 qp_encode(&cout, &cin, QP_ISHEAD);
473 wr = cout.l + charsetlen + 7;
474 jqp_retest:
475 if (col <= maxcol && wr <= maxcol - col) {
476 if (lastspc) {
477 /* TODO because we included the WS in the encoded str,
478 * TODO put SP only??
479 * TODO RFC: "any 'linear-white-space' that separates
480 * TODO a pair of adjacent 'encoded-word's is ignored" */
481 putc(' ', fo);
482 ++sz;
483 ++col;
485 fprintf(fo, "=?%s?Q?%.*s?=",
486 (highbit ? charset : charset7), (int)cout.l, cout.s);
487 sz += wr;
488 col += wr;
489 break;
490 } else if (col > 1) {
491 /* TODO assuming SP separator, ignore *lastspc* !?? */
492 broken = TRU1;
493 if (lastspc != NULL) {
494 putc('\n', fo);
495 ++sz;
496 col = 0;
497 } else {
498 fputs("\n ", fo);
499 sz += 2;
500 col = 1;
502 maxcol = 76;
503 goto jqp_retest;
504 } else {
505 for (;;) { /* XXX */
506 wend -= 4;
507 assert(wend > wbeg);
508 if (wr - 4 < maxcol)
509 break;
510 wr -= 4;
514 if (cout.s != NULL)
515 free(cout.s);
516 lastwordend = wend;
517 } else {
518 if (col && PTR2SIZE(wend - wbeg) > maxcol - col) {
519 putc('\n', fo);
520 ++sz;
521 col = 0;
522 maxcol = 76;
523 if (lastspc == NULL) {
524 putc(' ', fo);
525 ++sz;
526 --maxcol;
527 } else
528 maxcol -= PTR2SIZE(wbeg - lastspc);
530 if (lastspc)
531 while (lastspc < wbeg) {
532 putc(*lastspc&0377, fo);
533 ++lastspc;
534 ++sz;
536 wr = fwrite(wbeg, sizeof *wbeg, PTR2SIZE(wend - wbeg), fo);
537 sz += wr;
538 col += wr;
539 lastwordend = NULL;
543 NYD_LEAVE;
544 return sz;
547 static size_t
548 convhdra(char const *str, size_t len, FILE *fp)
550 #ifdef HAVE_ICONV
551 struct str ciconv;
552 #endif
553 struct str cin;
554 size_t ret = 0;
555 NYD_ENTER;
557 cin.s = UNCONST(str);
558 cin.l = len;
559 #ifdef HAVE_ICONV
560 ciconv.s = NULL;
561 if (iconvd != (iconv_t)-1) {
562 ciconv.l = 0;
563 if (n_iconv_str(iconvd, &ciconv, &cin, NULL, FAL0) != 0)
564 goto jleave;
565 cin = ciconv;
567 #endif
568 ret = mime_write_tohdr(&cin, fp);
569 #ifdef HAVE_ICONV
570 jleave:
571 if (ciconv.s != NULL)
572 free(ciconv.s);
573 #endif
574 NYD_LEAVE;
575 return ret;
578 static size_t
579 mime_write_tohdr_a(struct str *in, FILE *f)
581 char const *cp, *lastcp;
582 size_t sz = 0;
583 NYD_ENTER;
585 in->s[in->l] = '\0';
586 lastcp = in->s;
587 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
588 sz += convhdra(lastcp, PTR2SIZE(cp - lastcp), f);
589 lastcp = cp;
590 } else
591 cp = in->s;
592 for ( ; *cp != '\0'; ++cp) {
593 switch (*cp) {
594 case '(':
595 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp + 1), f);
596 lastcp = ++cp;
597 cp = skip_comment(cp);
598 if (--cp > lastcp)
599 sz += convhdra(lastcp, PTR2SIZE(cp - lastcp), f);
600 lastcp = cp;
601 break;
602 case '"':
603 while (*cp) {
604 if (*++cp == '"')
605 break;
606 if (*cp == '\\' && cp[1] != '\0')
607 cp++;
609 break;
612 if (cp > lastcp)
613 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp), f);
614 NYD_LEAVE;
615 return sz;
618 static void
619 addstr(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
621 NYD_ENTER;
622 *buf = srealloc(*buf, *sz += len);
623 memcpy(&(*buf)[*pos], str, len);
624 *pos += len;
625 NYD_LEAVE;
628 static void
629 addconv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
631 struct str in, out;
632 NYD_ENTER;
634 in.s = UNCONST(str);
635 in.l = len;
636 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
637 addstr(buf, sz, pos, out.s, out.l);
638 free(out.s);
639 NYD_LEAVE;
642 FL char const *
643 charset_get_7bit(void)
645 char const *t;
646 NYD_ENTER;
648 if ((t = ok_vlook(charset_7bit)) == NULL)
649 t = CHARSET_7BIT;
650 NYD_LEAVE;
651 return t;
654 FL char const *
655 charset_get_8bit(void)
657 char const *t;
658 NYD_ENTER;
660 if ((t = ok_vlook(CHARSET_8BIT_OKEY)) == NULL)
661 t = CHARSET_8BIT;
662 NYD_LEAVE;
663 return t;
666 FL char const *
667 charset_get_lc(void)
669 char const *t;
670 NYD_ENTER;
672 if ((t = ok_vlook(ttycharset)) == NULL)
673 t = CHARSET_8BIT;
674 NYD_LEAVE;
675 return t;
678 FL void
679 charset_iter_reset(char const *a_charset_to_try_first)
681 char const *sarr[3];
682 size_t sarrl[3], len;
683 char *cp;
684 NYD_ENTER;
686 sarr[0] = a_charset_to_try_first;
687 #ifdef HAVE_ICONV
688 if ((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
689 ok_blook(sendcharsets_else_ttycharset))
690 sarr[1] = charset_get_lc();
691 #endif
692 sarr[2] = charset_get_8bit();
694 sarrl[2] = len = strlen(sarr[2]);
695 #ifdef HAVE_ICONV
696 if ((cp = UNCONST(sarr[1])) != NULL)
697 len += (sarrl[1] = strlen(cp));
698 else
699 sarrl[1] = 0;
700 if ((cp = UNCONST(sarr[0])) != NULL)
701 len += (sarrl[0] = strlen(cp));
702 else
703 sarrl[0] = 0;
704 #endif
706 _cs_iter_base = cp = salloc(len + 1 + 1 +1);
708 #ifdef HAVE_ICONV
709 if ((len = sarrl[0]) != 0) {
710 memcpy(cp, sarr[0], len);
711 cp[len] = ',';
712 cp += ++len;
714 if ((len = sarrl[1]) != 0) {
715 memcpy(cp, sarr[1], len);
716 cp[len] = ',';
717 cp += ++len;
719 #endif
720 len = sarrl[2];
721 memcpy(cp, sarr[2], len);
722 cp[len] = '\0';
723 _cs_iter = NULL;
724 NYD_LEAVE;
727 FL char const *
728 charset_iter_next(void)
730 char const *rv;
731 NYD_ENTER;
733 rv = _cs_iter = n_strsep(&_cs_iter_base, ',', TRU1);
734 NYD_LEAVE;
735 return rv;
738 FL char const *
739 charset_iter_current(void)
741 char const *rv;
742 NYD_ENTER;
744 rv = _cs_iter;
745 NYD_LEAVE;
746 return rv;
749 FL void
750 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
752 NYD_ENTER;
753 outer_storage[0] = _cs_iter_base;
754 outer_storage[1] = _cs_iter;
755 NYD_LEAVE;
758 FL void
759 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
761 NYD_ENTER;
762 _cs_iter_base = outer_storage[0];
763 _cs_iter = outer_storage[1];
764 NYD_LEAVE;
767 #ifdef HAVE_ICONV
768 FL char const *
769 need_hdrconv(struct header *hp, enum gfield w)
771 char const *ret = NULL;
772 NYD_ENTER;
774 if (w & GIDENT) {
775 if (hp->h_from != NULL) {
776 if (_name_highbit(hp->h_from))
777 goto jneeds;
778 } else if (_has_highbit(myaddrs(NULL)))
779 goto jneeds;
780 if (hp->h_organization) {
781 if (_has_highbit(hp->h_organization))
782 goto jneeds;
783 } else if (_has_highbit(ok_vlook(ORGANIZATION)))
784 goto jneeds;
785 if (hp->h_replyto) {
786 if (_name_highbit(hp->h_replyto))
787 goto jneeds;
788 } else if (_has_highbit(ok_vlook(replyto)))
789 goto jneeds;
790 if (hp->h_sender) {
791 if (_name_highbit(hp->h_sender))
792 goto jneeds;
793 } else if (_has_highbit(ok_vlook(sender)))
794 goto jneeds;
796 if ((w & GTO) && _name_highbit(hp->h_to))
797 goto jneeds;
798 if ((w & GCC) && _name_highbit(hp->h_cc))
799 goto jneeds;
800 if ((w & GBCC) && _name_highbit(hp->h_bcc))
801 goto jneeds;
802 if ((w & GSUBJECT) && _has_highbit(hp->h_subject))
803 jneeds:
804 ret = _CHARSET();
805 NYD_LEAVE;
806 return ret;
808 #endif /* HAVE_ICONV */
810 FL enum mimeenc
811 mime_getenc(char *p)
813 enum mimeenc rv;
814 NYD_ENTER;
816 if (is_this_enc(p, "7bit"))
817 rv = MIME_7B;
818 else if (is_this_enc(p, "8bit"))
819 rv = MIME_8B;
820 else if (is_this_enc(p, "base64"))
821 rv = MIME_B64;
822 else if (is_this_enc(p, "binary"))
823 rv = MIME_BIN;
824 else if (is_this_enc(p, "quoted-printable"))
825 rv = MIME_QP;
826 else
827 rv = MIME_NONE;
828 NYD_LEAVE;
829 return rv;
832 FL char *
833 mime_getparam(char const *param, char *h)
835 char *p = h, *q, *rv = NULL;
836 int c;
837 size_t sz;
838 NYD_ENTER;
840 sz = strlen(param);
841 if (!whitechar(*p)) {
842 c = '\0';
843 while (*p && (*p != ';' || c == '\\')) {
844 c = (c == '\\') ? '\0' : *p;
845 ++p;
847 if (*p++ == '\0')
848 goto jleave;
851 for (;;) {
852 while (whitechar(*p))
853 ++p;
854 if (!ascncasecmp(p, param, sz)) {
855 p += sz;
856 while (whitechar(*p))
857 ++p;
858 if (*p++ == '=')
859 break;
861 c = '\0';
862 while (*p != '\0' && (*p != ';' || c == '\\')) {
863 if (*p == '"' && c != '\\') {
864 ++p;
865 while (*p != '\0' && (*p != '"' || c == '\\')) {
866 c = (c == '\\') ? '\0' : *p;
867 ++p;
869 ++p;
870 } else {
871 c = (c == '\\') ? '\0' : *p;
872 ++p;
875 if (*p++ == '\0')
876 goto jleave;
878 while (whitechar(*p))
879 ++p;
881 q = p;
882 if (*p == '"') {
883 p++;
884 if ((q = strchr(p, '"')) == NULL)
885 goto jleave;
886 } else {
887 while (*q != '\0' && !whitechar(*q) && *q != ';')
888 ++q;
890 sz = PTR2SIZE(q - p);
891 rv = salloc(q - p +1);
892 memcpy(rv, p, sz);
893 rv[sz] = '\0';
894 jleave:
895 NYD_LEAVE;
896 return rv;
899 FL char *
900 mime_get_boundary(char *h, size_t *len)
902 char *q = NULL, *p;
903 size_t sz;
904 NYD_ENTER;
906 if ((p = mime_getparam("boundary", h)) != NULL) {
907 sz = strlen(p);
908 if (len != NULL)
909 *len = sz + 2;
910 q = salloc(sz + 2 +1);
911 q[0] = q[1] = '-';
912 memcpy(q + 2, p, sz);
913 *(q + sz + 2) = '\0';
915 NYD_LEAVE;
916 return q;
919 FL char *
920 mime_create_boundary(void)
922 char *bp;
923 NYD_ENTER;
925 bp = salloc(48);
926 snprintf(bp, 48, "=_%011lu=-%s=_", (ul_it)time_current.tc_time,
927 getrandstring(47 - (11 + 6)));
928 NYD_LEAVE;
929 return bp;
932 FL int
933 mime_classify_file(FILE *fp, char const **contenttype, char const **charset,
934 int *do_iconv)
936 /* TODO classify once only PLEASE PLEASE PLEASE */
937 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
938 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
939 * TODO and report that state to the outer world */
940 #define F_ "From "
941 #define F_SIZEOF (sizeof(F_) -1)
943 char f_buf[F_SIZEOF], *f_p = f_buf;
944 enum {
945 _CLEAN = 0, /* Plain RFC 2822 message */
946 _NCTT = 1<<0, /* *contenttype == NULL */
947 _ISTXT = 1<<1, /* *contenttype =~ text/ */
948 _ISTXTCOK = 1<<2, /* _ISTXT + *mime-allow-text-controls* */
949 _HIGHBIT = 1<<3, /* Not 7bit clean */
950 _LONGLINES = 1<<4, /* MIME_LINELEN_LIMIT exceed. */
951 _CTRLCHAR = 1<<5, /* Control characters seen */
952 _HASNUL = 1<<6, /* Contains \0 characters */
953 _NOTERMNL = 1<<7, /* Lacks a final newline */
954 _TRAILWS = 1<<8, /* Blanks before NL */
955 _FROM_ = 1<<9 /* ^From_ seen */
956 } ctt = _CLEAN;
957 enum conversion convert;
958 ssize_t curlen;
959 int c, lastc;
960 NYD_ENTER;
962 assert(ftell(fp) == 0x0l);
964 *do_iconv = 0;
966 if (*contenttype == NULL)
967 ctt = _NCTT;
968 else if (!ascncasecmp(*contenttype, "text/", 5))
969 ctt = ok_blook(mime_allow_text_controls) ? _ISTXT | _ISTXTCOK : _ISTXT;
970 convert = _conversion_by_encoding();
972 if (fsize(fp) == 0)
973 goto j7bit;
975 /* We have to inspect the file content */
976 for (curlen = 0, c = EOF;; ++curlen) {
977 lastc = c;
978 c = getc(fp);
980 if (c == '\0') {
981 ctt |= _HASNUL;
982 if (!(ctt & _ISTXTCOK))
983 break;
984 continue;
986 if (c == '\n' || c == EOF) {
987 if (curlen >= MIME_LINELEN_LIMIT)
988 ctt |= _LONGLINES;
989 if (c == EOF)
990 break;
991 if (blankchar(lastc))
992 ctt |= _TRAILWS;
993 f_p = f_buf;
994 curlen = -1;
995 continue;
997 /* A bit hairy is handling of \r=\x0D=CR.
998 * RFC 2045, 6.7:
999 * Control characters other than TAB, or CR and LF as parts of CRLF
1000 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
1001 * we cannot peek the next character. Thus right here, inspect the last
1002 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
1003 /*else*/ if (lastc == '\r')
1004 ctt |= _CTRLCHAR;
1006 /* Control character? XXX this is all ASCII here */
1007 if (c < 0x20 || c == 0x7F) {
1008 /* RFC 2045, 6.7, as above ... */
1009 if (c != '\t' && c != '\r')
1010 ctt |= _CTRLCHAR;
1011 /* If there is a escape sequence in backslash notation defined for
1012 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
1013 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
1014 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
1015 * \e=\x1B=ESC */
1016 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
1017 continue;
1018 ctt |= _HASNUL; /* Force base64 */
1019 if (!(ctt & _ISTXTCOK))
1020 break;
1021 } else if ((ui8_t)c & 0x80) {
1022 ctt |= _HIGHBIT;
1023 /* TODO count chars with HIGHBIT? libmagic?
1024 * TODO try encode part - base64 if bails? */
1025 if (!(ctt & (_NCTT | _ISTXT))) { /* TODO _NCTT?? */
1026 ctt |= _HASNUL; /* Force base64 */
1027 break;
1029 } else if (!(ctt & _FROM_) && UICMP(z, curlen, <, F_SIZEOF)) {
1030 *f_p++ = (char)c;
1031 if (UICMP(z, curlen, ==, F_SIZEOF - 1) &&
1032 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
1033 !memcmp(f_buf, F_, F_SIZEOF))
1034 ctt |= _FROM_;
1037 if (lastc != '\n')
1038 ctt |= _NOTERMNL;
1039 rewind(fp);
1041 if (ctt & _HASNUL) {
1042 convert = CONV_TOB64;
1043 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1044 * on request; else enforce what file(1)/libmagic(3) would suggest */
1045 if (ctt & _ISTXTCOK)
1046 goto jcharset;
1047 if (ctt & (_NCTT | _ISTXT))
1048 *contenttype = "application/octet-stream";
1049 if (*charset == NULL)
1050 *charset = "binary";
1051 goto jleave;
1054 if (ctt & (_LONGLINES | _CTRLCHAR | _NOTERMNL | _TRAILWS | _FROM_)) {
1055 convert = CONV_TOQP;
1056 goto jstepi;
1058 if (ctt & _HIGHBIT) {
1059 jstepi:
1060 if (ctt & (_NCTT | _ISTXT))
1061 *do_iconv = ((ctt & _HIGHBIT) != 0);
1062 } else
1063 j7bit:
1064 convert = CONV_7BIT;
1065 if (ctt & _NCTT)
1066 *contenttype = "text/plain";
1068 /* Not an attachment with specified charset? */
1069 jcharset:
1070 if (*charset == NULL)
1071 *charset = (ctt & _HIGHBIT) ? _CHARSET() : charset_get_7bit();
1072 jleave:
1073 NYD_LEAVE;
1074 return convert;
1075 #undef F_
1076 #undef F_SIZEOF
1079 FL enum mimecontent
1080 mime_classify_content_of_part(struct mimepart const *mip)
1082 enum mimecontent mc;
1083 char const *ct;
1084 NYD_ENTER;
1086 mc = MIME_UNKNOWN;
1087 ct = mip->m_ct_type_plain;
1089 if (!asccasecmp(ct, "application/octet-stream") &&
1090 mip->m_filename != NULL && ok_blook(mime_counter_evidence)) {
1091 ct = mime_classify_content_type_by_fileext(mip->m_filename);
1092 if (ct == NULL)
1093 /* TODO how about let *mime-counter-evidence* have
1094 * TODO a value, and if set, saving the attachment in
1095 * TODO a temporary file that mime_classify_file() can
1096 * TODO examine, and using MIME_TEXT if that gives us
1097 * TODO something that seems to be human readable?! */
1098 goto jleave;
1100 if (strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
1101 mc = MIME_TEXT;
1102 else if (!asccasecmp(ct, "text/plain"))
1103 mc = MIME_TEXT_PLAIN;
1104 else if (!asccasecmp(ct, "text/html"))
1105 mc = MIME_TEXT_HTML;
1106 else if (!ascncasecmp(ct, "text/", 5))
1107 mc = MIME_TEXT;
1108 else if (!asccasecmp(ct, "message/rfc822"))
1109 mc = MIME_822;
1110 else if (!ascncasecmp(ct, "message/", 8))
1111 mc = MIME_MESSAGE;
1112 else if (!asccasecmp(ct, "multipart/alternative"))
1113 mc = MIME_ALTERNATIVE;
1114 else if (!asccasecmp(ct, "multipart/digest"))
1115 mc = MIME_DIGEST;
1116 else if (!ascncasecmp(ct, "multipart/", 10))
1117 mc = MIME_MULTI;
1118 else if (!asccasecmp(ct, "application/x-pkcs7-mime") ||
1119 !asccasecmp(ct, "application/pkcs7-mime"))
1120 mc = MIME_PKCS7;
1121 jleave:
1122 NYD_LEAVE;
1123 return mc;
1126 FL char *
1127 mime_classify_content_type_by_fileext(char const *name)
1129 char *content = NULL;
1130 struct mtnode *mtn;
1131 size_t nlen;
1132 NYD_ENTER;
1134 if ((name = strrchr(name, '.')) == NULL || *++name == '\0')
1135 goto jleave;
1137 if (_mt_list == NULL)
1138 _mt_init();
1140 nlen = strlen(name);
1141 for (mtn = _mt_list; mtn != NULL; mtn = mtn->mt_next) {
1142 char const *ext = mtn->mt_line + mtn->mt_mtlen + 1,
1143 *cp = ext;
1144 do {
1145 while (!whitechar(*cp) && *cp != '\0')
1146 ++cp;
1147 /* Better to do case-insensitive comparison on extension, since the
1148 * RFC doesn't specify case of attribute values? */
1149 if (nlen == PTR2SIZE(cp - ext) && !ascncasecmp(name, ext, nlen)) {
1150 content = savestrbuf(mtn->mt_line, mtn->mt_mtlen);
1151 goto jleave;
1153 while (whitechar(*cp) && *cp != '\0')
1154 ++cp;
1155 ext = cp;
1156 } while (*ext != '\0');
1158 jleave:
1159 NYD_LEAVE;
1160 return content;
1163 FL int
1164 c_mimetypes(void *v)
1166 char **argv = v;
1167 struct mtnode *mtn;
1168 NYD_ENTER;
1170 if (*argv == NULL)
1171 goto jlist;
1172 if (argv[1] != NULL)
1173 goto jerr;
1174 if (!asccasecmp(*argv, "show"))
1175 goto jlist;
1176 if (!asccasecmp(*argv, "clear"))
1177 goto jclear;
1178 jerr:
1179 fprintf(stderr, "Synopsis: mimetypes: %s\n",
1180 tr(418, "Either <show> (default) or <clear> the mime.types cache"));
1181 v = NULL;
1182 jleave:
1183 NYD_LEAVE;
1184 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1186 jlist: {
1187 FILE *fp;
1188 size_t l;
1190 if (_mt_list == NULL)
1191 _mt_init();
1193 if ((fp = Ftmp(NULL, "mimelist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
1194 NULL) {
1195 perror("tmpfile");
1196 v = NULL;
1197 goto jleave;
1200 for (l = 0, mtn = _mt_list; mtn != NULL; ++l, mtn = mtn->mt_next)
1201 fprintf(fp, "%s\t%s\n", mtn->mt_line, mtn->mt_line + mtn->mt_mtlen + 1);
1203 page_or_print(fp, l);
1204 Fclose(fp);
1206 goto jleave;
1208 jclear:
1209 while ((mtn = _mt_list) != NULL) {
1210 _mt_list = mtn->mt_next;
1211 free(mtn);
1213 goto jleave;
1216 FL void
1217 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
1219 /* TODO mime_fromhdr(): is called with strings that contain newlines;
1220 * TODO this is the usual newline problem all around the codebase;
1221 * TODO i.e., if we strip it, then the display misses it ;>
1222 * TODO this is why it is so messy and why S-nail v14.2 plus additional
1223 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
1224 * TODO why our display reflects what is contained in the message: the 1:1
1225 * TODO relationship of message content and display!
1226 * TODO instead a header line should be decoded to what it is (a single
1227 * TODO line that is) and it should be objective to the backend wether
1228 * TODO it'll be folded to fit onto the display or not, e.g., for search
1229 * TODO purposes etc. then the only condition we have to honour in here
1230 * TODO is that whitespace in between multiple adjacent MIME encoded words
1231 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
1232 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
1233 struct str cin, cout;
1234 char *p, *op, *upper, *cs, *cbeg;
1235 ui32_t convert, lastenc, lastoutl;
1236 #ifdef HAVE_ICONV
1237 char const *tcs;
1238 iconv_t fhicd = (iconv_t)-1;
1239 #endif
1240 NYD_ENTER;
1242 out->l = 0;
1243 if (in->l == 0) {
1244 *(out->s = smalloc(1)) = '\0';
1245 goto jleave;
1247 out->s = NULL;
1249 #ifdef HAVE_ICONV
1250 tcs = charset_get_lc();
1251 #endif
1252 p = in->s;
1253 upper = p + in->l;
1254 lastenc = lastoutl = 0;
1256 while (p < upper) {
1257 op = p;
1258 if (*p == '=' && *(p + 1) == '?') {
1259 p += 2;
1260 cbeg = p;
1261 while (p < upper && *p != '?')
1262 ++p; /* strip charset */
1263 if (p >= upper)
1264 goto jnotmime;
1265 cs = salloc(PTR2SIZE(++p - cbeg));
1266 memcpy(cs, cbeg, PTR2SIZE(p - cbeg - 1));
1267 cs[p - cbeg - 1] = '\0';
1268 #ifdef HAVE_ICONV
1269 if (fhicd != (iconv_t)-1)
1270 n_iconv_close(fhicd);
1271 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
1272 #endif
1273 switch (*p) {
1274 case 'B': case 'b':
1275 convert = CONV_FROMB64;
1276 break;
1277 case 'Q': case 'q':
1278 convert = CONV_FROMQP;
1279 break;
1280 default: /* invalid, ignore */
1281 goto jnotmime;
1283 if (*++p != '?')
1284 goto jnotmime;
1285 cin.s = ++p;
1286 cin.l = 1;
1287 for (;;) {
1288 if (PTRCMP(p + 1, >=, upper))
1289 goto jnotmime;
1290 if (*p++ == '?' && *p == '=')
1291 break;
1292 ++cin.l;
1294 ++p;
1295 --cin.l;
1297 cout.s = NULL;
1298 cout.l = 0;
1299 if (convert == CONV_FROMB64) {
1300 /* XXX Take care for, and strip LF from
1301 * XXX [Invalid Base64 encoding ignored] */
1302 if (b64_decode(&cout, &cin, NULL) == STOP &&
1303 cout.s[cout.l - 1] == '\n')
1304 --cout.l;
1305 } else
1306 qp_decode(&cout, &cin, NULL);
1308 out->l = lastenc;
1309 #ifdef HAVE_ICONV
1310 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
1311 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
1312 convert = n_iconv_str(fhicd, &cin, &cout, NULL, TRU1);
1313 out = n_str_add(out, &cin);
1314 if (convert) /* EINVAL at EOS */
1315 out = n_str_add_buf(out, "?", 1);
1316 free(cin.s);
1317 } else
1318 #endif
1319 out = n_str_add(out, &cout);
1320 lastenc = lastoutl = out->l;
1321 free(cout.s);
1322 } else
1323 jnotmime: {
1324 bool_t onlyws;
1326 p = op;
1327 onlyws = (lastenc > 0);
1328 for (;;) {
1329 if (++op == upper)
1330 break;
1331 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
1332 break;
1333 if (onlyws && !blankchar(*op))
1334 onlyws = FAL0;
1337 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
1338 p = op;
1339 if (!onlyws || lastoutl != lastenc)
1340 lastenc = out->l;
1341 lastoutl = out->l;
1344 out->s[out->l] = '\0';
1346 if (flags & TD_ISPR) {
1347 makeprint(out, &cout);
1348 free(out->s);
1349 *out = cout;
1351 if (flags & TD_DELCTRL)
1352 out->l = delctrl(out->s, out->l);
1353 #ifdef HAVE_ICONV
1354 if (fhicd != (iconv_t)-1)
1355 n_iconv_close(fhicd);
1356 #endif
1357 jleave:
1358 NYD_LEAVE;
1359 return;
1362 FL char *
1363 mime_fromaddr(char const *name)
1365 char const *cp, *lastcp;
1366 char *res = NULL;
1367 size_t ressz = 1, rescur = 0;
1368 NYD_ENTER;
1370 if (name == NULL)
1371 goto jleave;
1372 if (*name == '\0') {
1373 res = savestr(name);
1374 goto jleave;
1377 if ((cp = routeaddr(name)) != NULL && cp > name) {
1378 addconv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
1379 lastcp = cp;
1380 } else
1381 cp = lastcp = name;
1383 for ( ; *cp; ++cp) {
1384 switch (*cp) {
1385 case '(':
1386 addstr(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
1387 lastcp = ++cp;
1388 cp = skip_comment(cp);
1389 if (--cp > lastcp)
1390 addconv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1391 lastcp = cp;
1392 break;
1393 case '"':
1394 while (*cp) {
1395 if (*++cp == '"')
1396 break;
1397 if (*cp == '\\' && cp[1] != '\0')
1398 ++cp;
1400 break;
1403 if (cp > lastcp)
1404 addstr(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1405 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1406 if (rescur == 0)
1407 res = UNCONST("");
1408 else
1409 res[rescur] = '\0';
1410 { char *x = res;
1411 res = savestr(res);
1412 free(x);
1414 jleave:
1415 NYD_LEAVE;
1416 return res;
1419 FL ssize_t
1420 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1421 enum tdflags dflags, struct str *rest)
1423 ssize_t rv;
1424 struct quoteflt *qf;
1425 NYD_ENTER;
1427 quoteflt_reset(qf = quoteflt_dummy(), f);
1428 rv = mime_write(ptr, size, f, convert, dflags, qf, rest);
1429 assert(quoteflt_flush(qf) == 0);
1430 NYD_LEAVE;
1431 return rv;
1434 FL ssize_t
1435 mime_write(char const *ptr, size_t size, FILE *f,
1436 enum conversion convert, enum tdflags dflags,
1437 struct quoteflt *qf, struct str *rest)
1439 /* TODO note: after send/MIME layer rewrite we will have a string pool
1440 * TODO so that memory allocation count drops down massively; for now,
1441 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1442 struct str in, out;
1443 ssize_t sz;
1444 int state;
1445 NYD_ENTER;
1447 in.s = UNCONST(ptr);
1448 in.l = size;
1449 out.s = NULL;
1450 out.l = 0;
1452 dflags |= _TD_BUFCOPY;
1453 if ((sz = size) == 0) {
1454 if (rest != NULL && rest->l != 0)
1455 goto jconvert;
1456 goto jleave;
1459 #ifdef HAVE_ICONV
1460 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1461 (convert == CONV_TOQP || convert == CONV_8BIT ||
1462 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1463 if (n_iconv_str(iconvd, &out, &in, NULL, FAL0) != 0) {
1464 /* XXX report conversion error? */;
1465 sz = -1;
1466 goto jleave;
1468 in = out;
1469 out.s = NULL;
1470 dflags &= ~_TD_BUFCOPY;
1472 #endif
1474 jconvert:
1475 switch (convert) {
1476 case CONV_FROMQP:
1477 state = qp_decode(&out, &in, rest);
1478 goto jqpb64_dec;
1479 case CONV_TOQP:
1480 qp_encode(&out, &in, QP_NONE);
1481 goto jqpb64_enc;
1482 case CONV_8BIT:
1483 sz = quoteflt_push(qf, in.s, in.l);
1484 break;
1485 case CONV_FROMB64:
1486 rest = NULL;
1487 /* FALLTHRU */
1488 case CONV_FROMB64_T:
1489 state = b64_decode(&out, &in, rest);
1490 jqpb64_dec:
1491 if ((sz = out.l) != 0) {
1492 ui32_t opl = qf->qf_pfix_len;
1493 if (state != OKAY)
1494 qf->qf_pfix_len = 0;
1495 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), rest, qf);
1496 qf->qf_pfix_len = opl;
1498 if (state != OKAY)
1499 sz = -1;
1500 break;
1501 case CONV_TOB64:
1502 b64_encode(&out, &in, B64_LF | B64_MULTILINE);
1503 jqpb64_enc:
1504 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1505 if (sz != (ssize_t)out.l)
1506 sz = -1;
1507 break;
1508 case CONV_FROMHDR:
1509 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1510 sz = quoteflt_push(qf, out.s, out.l);
1511 break;
1512 case CONV_TOHDR:
1513 sz = mime_write_tohdr(&in, f);
1514 break;
1515 case CONV_TOHDR_A:
1516 sz = mime_write_tohdr_a(&in, f);
1517 break;
1518 default:
1519 sz = _fwrite_td(&in, dflags, NULL, qf);
1520 break;
1522 jleave:
1523 if (out.s != NULL)
1524 free(out.s);
1525 if (in.s != ptr)
1526 free(in.s);
1527 NYD_LEAVE;
1528 return sz;
1531 /* vim:set fenc=utf-8:s-it-mode */