Fix QP encoding canary violation (Peter Hofmann)..
[s-mailx.git] / mime.c
blobaf3a22bd8d09f58416b82eca5340d856e0d21dd4
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 /* Use base64 encoding if more than 25% of the line must be quoted,
418 * otherwise step over the data and encode quoted-printable as necessary */
419 if (quoteany << 2 > in->l) {
420 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
421 wend = upper;
422 cin.s = UNCONST(wbeg);
423 for (;;) {
424 cin.l = PTR2SIZE(wend - wbeg);
425 if (cin.l * 4/3 + 7 + charsetlen < maxcol - col) {
426 cout.s = buf;
427 cout.l = sizeof buf;
428 wr = fprintf(fo, "=?%s?B?%s?=", (highbit ? charset : charset7),
429 b64_encode(&cout, &cin, B64_BUF)->s);
430 sz += wr;
431 col += wr;
432 if (wend < upper) {
433 fwrite("\n ", sizeof(char), 2, fo);
434 sz += 2;
435 col = 0;
436 maxcol = 76;
438 break;
439 } else {
440 if (col) {
441 fprintf(fo, "\n ");
442 sz += 2;
443 col = 0;
444 maxcol = 76;
445 } else
446 wend -= 4;
450 } else {
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 *mpp)
1125 enum mimecontent mc;
1126 char const *ct;
1127 union {char const *cp; long l;} mce;
1128 NYD_ENTER;
1130 mc = MIME_UNKNOWN;
1131 ct = mpp->m_ct_type_plain;
1133 if (!asccasecmp(ct, "application/octet-stream") && mpp->m_filename != NULL &&
1134 (mce.cp = ok_vlook(mime_counter_evidence)) != NULL) {
1135 ct = mime_classify_content_type_by_fileext(mpp->m_filename);
1136 if (ct == NULL)
1137 /* TODO add bit 1 to possible *mime-counter-evidence* value
1138 * TODO and let it mean to save the attachment in
1139 * TODO a temporary file that mime_classify_file() can
1140 * TODO examine, and using MIME_TEXT if that gives us
1141 * TODO something that seems to be human readable?! */
1142 goto jleave;
1144 mce.l = strtol(mce.cp, NULL, 0);
1145 if (mce.l & MIMECE_USR_OVWR)
1146 mpp->m_ct_type_usr_ovwr = UNCONST(ct);
1149 if (strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
1150 mc = MIME_TEXT;
1151 else if (!asccasecmp(ct, "text/plain"))
1152 mc = MIME_TEXT_PLAIN;
1153 else if (!asccasecmp(ct, "text/html"))
1154 mc = MIME_TEXT_HTML;
1155 else if (!ascncasecmp(ct, "text/", 5))
1156 mc = MIME_TEXT;
1157 else if (!asccasecmp(ct, "message/rfc822"))
1158 mc = MIME_822;
1159 else if (!ascncasecmp(ct, "message/", 8))
1160 mc = MIME_MESSAGE;
1161 else if (!asccasecmp(ct, "multipart/alternative"))
1162 mc = MIME_ALTERNATIVE;
1163 else if (!asccasecmp(ct, "multipart/digest"))
1164 mc = MIME_DIGEST;
1165 else if (!ascncasecmp(ct, "multipart/", 10))
1166 mc = MIME_MULTI;
1167 else if (!asccasecmp(ct, "application/x-pkcs7-mime") ||
1168 !asccasecmp(ct, "application/pkcs7-mime"))
1169 mc = MIME_PKCS7;
1170 jleave:
1171 NYD_LEAVE;
1172 return mc;
1175 FL char *
1176 mime_classify_content_type_by_fileext(char const *name)
1178 char *content = NULL;
1179 struct mtnode *mtn;
1180 size_t nlen;
1181 NYD_ENTER;
1183 /* TODO mime_classify(): mime.types(5) has *-gz but we search dot!
1184 * TODO i.e., we cannot handle files like dubidu.tar-gz; need globs! */
1185 /* TODO even better: regex, with fast lists for (README|INSTALL|NEWS) etc,
1186 * TODO that, also add some mechanism for filenames without extension */
1187 if ((name = strrchr(name, '.')) == NULL || *++name == '\0')
1188 goto jleave;
1190 if (_mt_list == NULL)
1191 _mt_init();
1192 if (NELEM(_mt_bltin) == 0 && _mt_list == (struct mtnode*)-1)
1193 goto jleave;
1195 nlen = strlen(name);
1196 for (mtn = _mt_list; mtn != NULL; mtn = mtn->mt_next) {
1197 char const *ext = mtn->mt_line + mtn->mt_mtlen + 1,
1198 *cp = ext;
1199 do {
1200 while (!whitechar(*cp) && *cp != '\0')
1201 ++cp;
1202 /* Better to do case-insensitive comparison on extension, since the
1203 * RFC doesn't specify case of attribute values? */
1204 if (nlen == PTR2SIZE(cp - ext) && !ascncasecmp(name, ext, nlen)) {
1205 content = savestrbuf(mtn->mt_line, mtn->mt_mtlen);
1206 goto jleave;
1208 while (whitechar(*cp) && *cp != '\0')
1209 ++cp;
1210 ext = cp;
1211 } while (*ext != '\0');
1213 jleave:
1214 NYD_LEAVE;
1215 return content;
1218 FL char *
1219 mimepart_get_handler(struct mimepart const *mpp)
1221 #define __S "pipe-"
1222 #define __L (sizeof(__S) -1)
1223 char const *es, *cs;
1224 size_t el, cl, l;
1225 char *buf, *rv;
1226 NYD_ENTER;
1228 /* TODO some mechanism for filenames without extension */
1229 el = ((es = mpp->m_filename) != NULL && (es = strrchr(es, '.')) != NULL &&
1230 *++es != '\0') ? strlen(es) : 0;
1231 cl = ((cs = mpp->m_ct_type_usr_ovwr) != NULL ||
1232 (cs = mpp->m_ct_type_plain) != NULL) ? strlen(cs) : 0;
1233 if ((l = MAX(el, cl)) == 0) {
1234 rv = NULL;
1235 goto jleave;
1238 buf = ac_alloc(__L + l +1);
1239 memcpy(buf, __S, __L);
1241 /* File-extension handlers take precedence.
1242 * Yes, we really "fail" here for file extensions which clash MIME types */
1243 if (el > 0) {
1244 memcpy(buf + __L, es, el +1);
1245 for (rv = buf + __L; *rv != '\0'; ++rv)
1246 *rv = lowerconv(*rv);
1248 if ((rv = vok_vlook(buf)) != NULL)
1249 goto jok;
1252 /* Then MIME Content-Type: */
1253 if (cl > 0) {
1254 memcpy(buf + __L, cs, cl +1);
1255 for (rv = buf + __L; *rv != '\0'; ++rv)
1256 *rv = lowerconv(*rv);
1258 if ((rv = vok_vlook(buf)) != NULL)
1259 goto jok;
1262 rv = NULL;
1263 jok:
1264 ac_free(buf);
1265 jleave:
1266 NYD_LEAVE;
1267 return rv;
1268 #undef __L
1269 #undef __S
1272 FL int
1273 c_mimetypes(void *v)
1275 char **argv = v;
1276 struct mtnode *mtn;
1277 NYD_ENTER;
1279 if (*argv == NULL)
1280 goto jlist;
1281 if (argv[1] != NULL)
1282 goto jerr;
1283 if (!asccasecmp(*argv, "show"))
1284 goto jlist;
1285 if (!asccasecmp(*argv, "clear"))
1286 goto jclear;
1287 jerr:
1288 fprintf(stderr, "Synopsis: mimetypes: %s\n",
1289 _("Either <show> (default) or <clear> the mime.types cache"));
1290 v = NULL;
1291 jleave:
1292 NYD_LEAVE;
1293 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1295 jlist: {
1296 FILE *fp;
1297 size_t l;
1299 if (_mt_list == NULL)
1300 _mt_init();
1301 if (NELEM(_mt_bltin) == 0 && _mt_list == (struct mtnode*)-1) {
1302 fprintf(stderr, _("Interpolate what file?\n"));
1303 v = NULL;
1304 goto jleave;
1307 if ((fp = Ftmp(NULL, "mimelist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
1308 NULL) {
1309 perror("tmpfile");
1310 v = NULL;
1311 goto jleave;
1314 for (l = 0, mtn = _mt_list; mtn != NULL; ++l, mtn = mtn->mt_next)
1315 fprintf(fp, "%s\t%s\n", mtn->mt_line, mtn->mt_line + mtn->mt_mtlen + 1);
1317 page_or_print(fp, l);
1318 Fclose(fp);
1320 goto jleave;
1322 jclear:
1323 if (NELEM(_mt_bltin) == 0 && _mt_list == (struct mtnode*)-1)
1324 _mt_list = NULL;
1325 while ((mtn = _mt_list) != NULL) {
1326 _mt_list = mtn->mt_next;
1327 free(mtn);
1329 goto jleave;
1332 FL void
1333 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
1335 /* TODO mime_fromhdr(): is called with strings that contain newlines;
1336 * TODO this is the usual newline problem all around the codebase;
1337 * TODO i.e., if we strip it, then the display misses it ;>
1338 * TODO this is why it is so messy and why S-nail v14.2 plus additional
1339 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
1340 * TODO why our display reflects what is contained in the message: the 1:1
1341 * TODO relationship of message content and display!
1342 * TODO instead a header line should be decoded to what it is (a single
1343 * TODO line that is) and it should be objective to the backend wether
1344 * TODO it'll be folded to fit onto the display or not, e.g., for search
1345 * TODO purposes etc. then the only condition we have to honour in here
1346 * TODO is that whitespace in between multiple adjacent MIME encoded words
1347 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
1348 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
1349 struct str cin, cout;
1350 char *p, *op, *upper, *cs, *cbeg;
1351 ui32_t convert, lastenc, lastoutl;
1352 #ifdef HAVE_ICONV
1353 char const *tcs;
1354 iconv_t fhicd = (iconv_t)-1;
1355 #endif
1356 NYD_ENTER;
1358 out->l = 0;
1359 if (in->l == 0) {
1360 *(out->s = smalloc(1)) = '\0';
1361 goto jleave;
1363 out->s = NULL;
1365 #ifdef HAVE_ICONV
1366 tcs = charset_get_lc();
1367 #endif
1368 p = in->s;
1369 upper = p + in->l;
1370 lastenc = lastoutl = 0;
1372 while (p < upper) {
1373 op = p;
1374 if (*p == '=' && *(p + 1) == '?') {
1375 p += 2;
1376 cbeg = p;
1377 while (p < upper && *p != '?')
1378 ++p; /* strip charset */
1379 if (p >= upper)
1380 goto jnotmime;
1381 cs = salloc(PTR2SIZE(++p - cbeg));
1382 memcpy(cs, cbeg, PTR2SIZE(p - cbeg - 1));
1383 cs[p - cbeg - 1] = '\0';
1384 #ifdef HAVE_ICONV
1385 if (fhicd != (iconv_t)-1)
1386 n_iconv_close(fhicd);
1387 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
1388 #endif
1389 switch (*p) {
1390 case 'B': case 'b':
1391 convert = CONV_FROMB64;
1392 break;
1393 case 'Q': case 'q':
1394 convert = CONV_FROMQP;
1395 break;
1396 default: /* invalid, ignore */
1397 goto jnotmime;
1399 if (*++p != '?')
1400 goto jnotmime;
1401 cin.s = ++p;
1402 cin.l = 1;
1403 for (;;) {
1404 if (PTRCMP(p + 1, >=, upper))
1405 goto jnotmime;
1406 if (*p++ == '?' && *p == '=')
1407 break;
1408 ++cin.l;
1410 ++p;
1411 --cin.l;
1413 cout.s = NULL;
1414 cout.l = 0;
1415 if (convert == CONV_FROMB64) {
1416 /* XXX Take care for, and strip LF from
1417 * XXX [Invalid Base64 encoding ignored] */
1418 if (b64_decode(&cout, &cin, NULL) == STOP &&
1419 cout.s[cout.l - 1] == '\n')
1420 --cout.l;
1421 } else
1422 qp_decode(&cout, &cin, NULL);
1424 out->l = lastenc;
1425 #ifdef HAVE_ICONV
1426 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
1427 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
1428 convert = n_iconv_str(fhicd, &cin, &cout, NULL, TRU1);
1429 out = n_str_add(out, &cin);
1430 if (convert) /* EINVAL at EOS */
1431 out = n_str_add_buf(out, "?", 1);
1432 free(cin.s);
1433 } else
1434 #endif
1435 out = n_str_add(out, &cout);
1436 lastenc = lastoutl = out->l;
1437 free(cout.s);
1438 } else
1439 jnotmime: {
1440 bool_t onlyws;
1442 p = op;
1443 onlyws = (lastenc > 0);
1444 for (;;) {
1445 if (++op == upper)
1446 break;
1447 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
1448 break;
1449 if (onlyws && !blankchar(*op))
1450 onlyws = FAL0;
1453 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
1454 p = op;
1455 if (!onlyws || lastoutl != lastenc)
1456 lastenc = out->l;
1457 lastoutl = out->l;
1460 out->s[out->l] = '\0';
1462 if (flags & TD_ISPR) {
1463 makeprint(out, &cout);
1464 free(out->s);
1465 *out = cout;
1467 if (flags & TD_DELCTRL)
1468 out->l = delctrl(out->s, out->l);
1469 #ifdef HAVE_ICONV
1470 if (fhicd != (iconv_t)-1)
1471 n_iconv_close(fhicd);
1472 #endif
1473 jleave:
1474 NYD_LEAVE;
1475 return;
1478 FL char *
1479 mime_fromaddr(char const *name)
1481 char const *cp, *lastcp;
1482 char *res = NULL;
1483 size_t ressz = 1, rescur = 0;
1484 NYD_ENTER;
1486 if (name == NULL)
1487 goto jleave;
1488 if (*name == '\0') {
1489 res = savestr(name);
1490 goto jleave;
1493 if ((cp = routeaddr(name)) != NULL && cp > name) {
1494 addconv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
1495 lastcp = cp;
1496 } else
1497 cp = lastcp = name;
1499 for ( ; *cp; ++cp) {
1500 switch (*cp) {
1501 case '(':
1502 addstr(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
1503 lastcp = ++cp;
1504 cp = skip_comment(cp);
1505 if (--cp > lastcp)
1506 addconv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1507 lastcp = cp;
1508 break;
1509 case '"':
1510 while (*cp) {
1511 if (*++cp == '"')
1512 break;
1513 if (*cp == '\\' && cp[1] != '\0')
1514 ++cp;
1516 break;
1519 if (cp > lastcp)
1520 addstr(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1521 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1522 if (rescur == 0)
1523 res = UNCONST("");
1524 else
1525 res[rescur] = '\0';
1526 { char *x = res;
1527 res = savestr(res);
1528 free(x);
1530 jleave:
1531 NYD_LEAVE;
1532 return res;
1535 FL ssize_t
1536 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1537 enum tdflags dflags, struct str *rest)
1539 ssize_t rv;
1540 struct quoteflt *qf;
1541 NYD_ENTER;
1543 quoteflt_reset(qf = quoteflt_dummy(), f);
1544 rv = mime_write(ptr, size, f, convert, dflags, qf, rest);
1545 assert(quoteflt_flush(qf) == 0);
1546 NYD_LEAVE;
1547 return rv;
1550 FL ssize_t
1551 mime_write(char const *ptr, size_t size, FILE *f,
1552 enum conversion convert, enum tdflags dflags,
1553 struct quoteflt *qf, struct str *rest)
1555 /* TODO note: after send/MIME layer rewrite we will have a string pool
1556 * TODO so that memory allocation count drops down massively; for now,
1557 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1558 struct str in, out;
1559 ssize_t sz;
1560 int state;
1561 NYD_ENTER;
1563 in.s = UNCONST(ptr);
1564 in.l = size;
1565 out.s = NULL;
1566 out.l = 0;
1568 dflags |= _TD_BUFCOPY;
1569 if ((sz = size) == 0) {
1570 if (rest != NULL && rest->l != 0)
1571 goto jconvert;
1572 goto jleave;
1575 #ifdef HAVE_ICONV
1576 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1577 (convert == CONV_TOQP || convert == CONV_8BIT ||
1578 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1579 if (n_iconv_str(iconvd, &out, &in, NULL, FAL0) != 0) {
1580 /* XXX report conversion error? */;
1581 sz = -1;
1582 goto jleave;
1584 in = out;
1585 out.s = NULL;
1586 dflags &= ~_TD_BUFCOPY;
1588 #endif
1590 jconvert:
1591 switch (convert) {
1592 case CONV_FROMQP:
1593 state = qp_decode(&out, &in, rest);
1594 goto jqpb64_dec;
1595 case CONV_TOQP:
1596 qp_encode(&out, &in, QP_NONE);
1597 goto jqpb64_enc;
1598 case CONV_8BIT:
1599 sz = quoteflt_push(qf, in.s, in.l);
1600 break;
1601 case CONV_FROMB64:
1602 rest = NULL;
1603 /* FALLTHRU */
1604 case CONV_FROMB64_T:
1605 state = b64_decode(&out, &in, rest);
1606 jqpb64_dec:
1607 if ((sz = out.l) != 0) {
1608 ui32_t opl = qf->qf_pfix_len;
1609 if (state != OKAY)
1610 qf->qf_pfix_len = 0;
1611 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), rest, qf);
1612 qf->qf_pfix_len = opl;
1614 if (state != OKAY)
1615 sz = -1;
1616 break;
1617 case CONV_TOB64:
1618 b64_encode(&out, &in, B64_LF | B64_MULTILINE);
1619 jqpb64_enc:
1620 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1621 if (sz != (ssize_t)out.l)
1622 sz = -1;
1623 break;
1624 case CONV_FROMHDR:
1625 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1626 sz = quoteflt_push(qf, out.s, out.l);
1627 break;
1628 case CONV_TOHDR:
1629 sz = mime_write_tohdr(&in, f);
1630 break;
1631 case CONV_TOHDR_A:
1632 sz = mime_write_tohdr_a(&in, f);
1633 break;
1634 default:
1635 sz = _fwrite_td(&in, dflags, NULL, qf);
1636 break;
1638 jleave:
1639 if (out.s != NULL)
1640 free(out.s);
1641 if (in.s != ptr)
1642 free(in.s);
1643 NYD_LEAVE;
1644 return sz;
1647 /* s-it-mode */