popen.c: use NYD2 where applicable
[s-mailx.git] / mime.c
blob515de142969d0357d224cf9a0c60c0fc014db683
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, _("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, _("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 /* TODO mime_classify(): mime.types(5) has *-gz but we search dot!
1178 * TODO i.e., we cannot handle files like dubidu.tar-gz; need globs! */
1179 if ((name = strrchr(name, '.')) == NULL || *++name == '\0')
1180 goto jleave;
1182 if (_mt_list == NULL)
1183 _mt_init();
1184 if (NELEM(_mt_bltin) == 0 && _mt_list == (struct mtnode*)-1)
1185 goto jleave;
1187 nlen = strlen(name);
1188 for (mtn = _mt_list; mtn != NULL; mtn = mtn->mt_next) {
1189 char const *ext = mtn->mt_line + mtn->mt_mtlen + 1,
1190 *cp = ext;
1191 do {
1192 while (!whitechar(*cp) && *cp != '\0')
1193 ++cp;
1194 /* Better to do case-insensitive comparison on extension, since the
1195 * RFC doesn't specify case of attribute values? */
1196 if (nlen == PTR2SIZE(cp - ext) && !ascncasecmp(name, ext, nlen)) {
1197 content = savestrbuf(mtn->mt_line, mtn->mt_mtlen);
1198 goto jleave;
1200 while (whitechar(*cp) && *cp != '\0')
1201 ++cp;
1202 ext = cp;
1203 } while (*ext != '\0');
1205 jleave:
1206 NYD_LEAVE;
1207 return content;
1210 FL int
1211 c_mimetypes(void *v)
1213 char **argv = v;
1214 struct mtnode *mtn;
1215 NYD_ENTER;
1217 if (*argv == NULL)
1218 goto jlist;
1219 if (argv[1] != NULL)
1220 goto jerr;
1221 if (!asccasecmp(*argv, "show"))
1222 goto jlist;
1223 if (!asccasecmp(*argv, "clear"))
1224 goto jclear;
1225 jerr:
1226 fprintf(stderr, "Synopsis: mimetypes: %s\n",
1227 _("Either <show> (default) or <clear> the mime.types cache"));
1228 v = NULL;
1229 jleave:
1230 NYD_LEAVE;
1231 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1233 jlist: {
1234 FILE *fp;
1235 size_t l;
1237 if (_mt_list == NULL)
1238 _mt_init();
1239 if (NELEM(_mt_bltin) == 0 && _mt_list == (struct mtnode*)-1) {
1240 fprintf(stderr, _("Interpolate what file?\n"));
1241 v = NULL;
1242 goto jleave;
1245 if ((fp = Ftmp(NULL, "mimelist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
1246 NULL) {
1247 perror("tmpfile");
1248 v = NULL;
1249 goto jleave;
1252 for (l = 0, mtn = _mt_list; mtn != NULL; ++l, mtn = mtn->mt_next)
1253 fprintf(fp, "%s\t%s\n", mtn->mt_line, mtn->mt_line + mtn->mt_mtlen + 1);
1255 page_or_print(fp, l);
1256 Fclose(fp);
1258 goto jleave;
1260 jclear:
1261 if (NELEM(_mt_bltin) == 0 && _mt_list == (struct mtnode*)-1)
1262 _mt_list = NULL;
1263 while ((mtn = _mt_list) != NULL) {
1264 _mt_list = mtn->mt_next;
1265 free(mtn);
1267 goto jleave;
1270 FL void
1271 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
1273 /* TODO mime_fromhdr(): is called with strings that contain newlines;
1274 * TODO this is the usual newline problem all around the codebase;
1275 * TODO i.e., if we strip it, then the display misses it ;>
1276 * TODO this is why it is so messy and why S-nail v14.2 plus additional
1277 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
1278 * TODO why our display reflects what is contained in the message: the 1:1
1279 * TODO relationship of message content and display!
1280 * TODO instead a header line should be decoded to what it is (a single
1281 * TODO line that is) and it should be objective to the backend wether
1282 * TODO it'll be folded to fit onto the display or not, e.g., for search
1283 * TODO purposes etc. then the only condition we have to honour in here
1284 * TODO is that whitespace in between multiple adjacent MIME encoded words
1285 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
1286 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
1287 struct str cin, cout;
1288 char *p, *op, *upper, *cs, *cbeg;
1289 ui32_t convert, lastenc, lastoutl;
1290 #ifdef HAVE_ICONV
1291 char const *tcs;
1292 iconv_t fhicd = (iconv_t)-1;
1293 #endif
1294 NYD_ENTER;
1296 out->l = 0;
1297 if (in->l == 0) {
1298 *(out->s = smalloc(1)) = '\0';
1299 goto jleave;
1301 out->s = NULL;
1303 #ifdef HAVE_ICONV
1304 tcs = charset_get_lc();
1305 #endif
1306 p = in->s;
1307 upper = p + in->l;
1308 lastenc = lastoutl = 0;
1310 while (p < upper) {
1311 op = p;
1312 if (*p == '=' && *(p + 1) == '?') {
1313 p += 2;
1314 cbeg = p;
1315 while (p < upper && *p != '?')
1316 ++p; /* strip charset */
1317 if (p >= upper)
1318 goto jnotmime;
1319 cs = salloc(PTR2SIZE(++p - cbeg));
1320 memcpy(cs, cbeg, PTR2SIZE(p - cbeg - 1));
1321 cs[p - cbeg - 1] = '\0';
1322 #ifdef HAVE_ICONV
1323 if (fhicd != (iconv_t)-1)
1324 n_iconv_close(fhicd);
1325 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
1326 #endif
1327 switch (*p) {
1328 case 'B': case 'b':
1329 convert = CONV_FROMB64;
1330 break;
1331 case 'Q': case 'q':
1332 convert = CONV_FROMQP;
1333 break;
1334 default: /* invalid, ignore */
1335 goto jnotmime;
1337 if (*++p != '?')
1338 goto jnotmime;
1339 cin.s = ++p;
1340 cin.l = 1;
1341 for (;;) {
1342 if (PTRCMP(p + 1, >=, upper))
1343 goto jnotmime;
1344 if (*p++ == '?' && *p == '=')
1345 break;
1346 ++cin.l;
1348 ++p;
1349 --cin.l;
1351 cout.s = NULL;
1352 cout.l = 0;
1353 if (convert == CONV_FROMB64) {
1354 /* XXX Take care for, and strip LF from
1355 * XXX [Invalid Base64 encoding ignored] */
1356 if (b64_decode(&cout, &cin, NULL) == STOP &&
1357 cout.s[cout.l - 1] == '\n')
1358 --cout.l;
1359 } else
1360 qp_decode(&cout, &cin, NULL);
1362 out->l = lastenc;
1363 #ifdef HAVE_ICONV
1364 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
1365 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
1366 convert = n_iconv_str(fhicd, &cin, &cout, NULL, TRU1);
1367 out = n_str_add(out, &cin);
1368 if (convert) /* EINVAL at EOS */
1369 out = n_str_add_buf(out, "?", 1);
1370 free(cin.s);
1371 } else
1372 #endif
1373 out = n_str_add(out, &cout);
1374 lastenc = lastoutl = out->l;
1375 free(cout.s);
1376 } else
1377 jnotmime: {
1378 bool_t onlyws;
1380 p = op;
1381 onlyws = (lastenc > 0);
1382 for (;;) {
1383 if (++op == upper)
1384 break;
1385 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
1386 break;
1387 if (onlyws && !blankchar(*op))
1388 onlyws = FAL0;
1391 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
1392 p = op;
1393 if (!onlyws || lastoutl != lastenc)
1394 lastenc = out->l;
1395 lastoutl = out->l;
1398 out->s[out->l] = '\0';
1400 if (flags & TD_ISPR) {
1401 makeprint(out, &cout);
1402 free(out->s);
1403 *out = cout;
1405 if (flags & TD_DELCTRL)
1406 out->l = delctrl(out->s, out->l);
1407 #ifdef HAVE_ICONV
1408 if (fhicd != (iconv_t)-1)
1409 n_iconv_close(fhicd);
1410 #endif
1411 jleave:
1412 NYD_LEAVE;
1413 return;
1416 FL char *
1417 mime_fromaddr(char const *name)
1419 char const *cp, *lastcp;
1420 char *res = NULL;
1421 size_t ressz = 1, rescur = 0;
1422 NYD_ENTER;
1424 if (name == NULL)
1425 goto jleave;
1426 if (*name == '\0') {
1427 res = savestr(name);
1428 goto jleave;
1431 if ((cp = routeaddr(name)) != NULL && cp > name) {
1432 addconv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
1433 lastcp = cp;
1434 } else
1435 cp = lastcp = name;
1437 for ( ; *cp; ++cp) {
1438 switch (*cp) {
1439 case '(':
1440 addstr(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
1441 lastcp = ++cp;
1442 cp = skip_comment(cp);
1443 if (--cp > lastcp)
1444 addconv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1445 lastcp = cp;
1446 break;
1447 case '"':
1448 while (*cp) {
1449 if (*++cp == '"')
1450 break;
1451 if (*cp == '\\' && cp[1] != '\0')
1452 ++cp;
1454 break;
1457 if (cp > lastcp)
1458 addstr(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1459 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1460 if (rescur == 0)
1461 res = UNCONST("");
1462 else
1463 res[rescur] = '\0';
1464 { char *x = res;
1465 res = savestr(res);
1466 free(x);
1468 jleave:
1469 NYD_LEAVE;
1470 return res;
1473 FL ssize_t
1474 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1475 enum tdflags dflags, struct str *rest)
1477 ssize_t rv;
1478 struct quoteflt *qf;
1479 NYD_ENTER;
1481 quoteflt_reset(qf = quoteflt_dummy(), f);
1482 rv = mime_write(ptr, size, f, convert, dflags, qf, rest);
1483 assert(quoteflt_flush(qf) == 0);
1484 NYD_LEAVE;
1485 return rv;
1488 FL ssize_t
1489 mime_write(char const *ptr, size_t size, FILE *f,
1490 enum conversion convert, enum tdflags dflags,
1491 struct quoteflt *qf, struct str *rest)
1493 /* TODO note: after send/MIME layer rewrite we will have a string pool
1494 * TODO so that memory allocation count drops down massively; for now,
1495 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1496 struct str in, out;
1497 ssize_t sz;
1498 int state;
1499 NYD_ENTER;
1501 in.s = UNCONST(ptr);
1502 in.l = size;
1503 out.s = NULL;
1504 out.l = 0;
1506 dflags |= _TD_BUFCOPY;
1507 if ((sz = size) == 0) {
1508 if (rest != NULL && rest->l != 0)
1509 goto jconvert;
1510 goto jleave;
1513 #ifdef HAVE_ICONV
1514 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1515 (convert == CONV_TOQP || convert == CONV_8BIT ||
1516 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1517 if (n_iconv_str(iconvd, &out, &in, NULL, FAL0) != 0) {
1518 /* XXX report conversion error? */;
1519 sz = -1;
1520 goto jleave;
1522 in = out;
1523 out.s = NULL;
1524 dflags &= ~_TD_BUFCOPY;
1526 #endif
1528 jconvert:
1529 switch (convert) {
1530 case CONV_FROMQP:
1531 state = qp_decode(&out, &in, rest);
1532 goto jqpb64_dec;
1533 case CONV_TOQP:
1534 qp_encode(&out, &in, QP_NONE);
1535 goto jqpb64_enc;
1536 case CONV_8BIT:
1537 sz = quoteflt_push(qf, in.s, in.l);
1538 break;
1539 case CONV_FROMB64:
1540 rest = NULL;
1541 /* FALLTHRU */
1542 case CONV_FROMB64_T:
1543 state = b64_decode(&out, &in, rest);
1544 jqpb64_dec:
1545 if ((sz = out.l) != 0) {
1546 ui32_t opl = qf->qf_pfix_len;
1547 if (state != OKAY)
1548 qf->qf_pfix_len = 0;
1549 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), rest, qf);
1550 qf->qf_pfix_len = opl;
1552 if (state != OKAY)
1553 sz = -1;
1554 break;
1555 case CONV_TOB64:
1556 b64_encode(&out, &in, B64_LF | B64_MULTILINE);
1557 jqpb64_enc:
1558 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1559 if (sz != (ssize_t)out.l)
1560 sz = -1;
1561 break;
1562 case CONV_FROMHDR:
1563 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1564 sz = quoteflt_push(qf, out.s, out.l);
1565 break;
1566 case CONV_TOHDR:
1567 sz = mime_write_tohdr(&in, f);
1568 break;
1569 case CONV_TOHDR_A:
1570 sz = mime_write_tohdr_a(&in, f);
1571 break;
1572 default:
1573 sz = _fwrite_td(&in, dflags, NULL, qf);
1574 break;
1576 jleave:
1577 if (out.s != NULL)
1578 free(out.s);
1579 if (in.s != ptr)
1580 free(in.s);
1581 NYD_LEAVE;
1582 return sz;
1585 /* vim:set fenc=utf-8:s-it-mode */