mime.c:mime_write_tohdr(): complete rewrite (Peter Hofmann)..
[s-mailx.git] / mime.c
blob99547bc161678d05e0de6a74ef84207cbc7df32a
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)
384 /* TODO mime_write_tohdr(): we don't know the name of our header->maxcol..
385 * TODO MIME/send layer rewrite: more available state!!
386 * TODO Because of this we cannot make a difference in between structured
387 * TODO and unstructured headers (RFC 2047, 5. (2))
388 * TODO NOT MULTIBYTE SAFE IF AN ENCODED WORD HAS TO BE SPLITTED!
389 * TODO To be better we had to mbtowc_l() (non-std! and no locale!!) and
390 * TODO work char-wise! -> S-CText..
391 * TODO The real problem for STD compatibility is however that "in" is
392 * TODO already iconv(3) encoded to the target character set! We could
393 * TODO also solve it (very expensively!) if we would narrow down to an
394 * TODO encoded word and then iconv(3)+CTencode in one go, in which case
395 * TODO multibyte errors could be catched!
396 * TODO All this doesn't take any care about RFC 2231, but simply and
397 * TODO falsely applies RFC 2047 and normal RFC 822/5322 folding to values
398 * TODO of parameters; part of the problem is that we just don't make a
399 * TODO difference in structured and unstructed headers, as long in TODO!
400 * TODO See also RFC 2047, 5., .." These are the ONLY locations"..
401 * TODO So, for now we require mutt(1)s "rfc2047_parameters=yes" support!!
402 * TODO BTW.: the purpose of QP is to allow non MIME-aware ASCII guys to
403 * TODO read the field nonetheless... */
404 enum {
405 /* Maximum line length *//* XXX we are too inflexible and could use
406 * XXX MIME_LINELEN unless an RFC 2047 encoding was actually used */
407 _MAXCOL = MIME_LINELEN_RFC2047
409 enum {
410 _FIRST = 1<<0, /* Nothing written yet, start of string */
411 _NO_QP = 1<<1, /* No quoted-printable allowed */
412 _NO_B64 = 1<<2, /* Ditto, base64 */
413 _ENC_LAST = 1<<3, /* Last round generated encoded word */
414 _SHOULD_BEE = 1<<4, /* Avoid lines longer than SHOULD via encoding */
415 _RND_SHIFT = 5,
416 _RND_MASK = (1<<_RND_SHIFT) - 1,
417 _SPACE = 1<<(_RND_SHIFT+1), /* Leading whitespace */
418 _8BIT = 1<<(_RND_SHIFT+2), /* High bit set */
419 _ENCODE = 1<<(_RND_SHIFT+3), /* Need encoding */
420 _ENC_B64 = 1<<(_RND_SHIFT+4), /* - let it be base64 */
421 _OVERLONG = 1<<(_RND_SHIFT+5) /* Temporarily rised limit */
422 } flags = _FIRST;
424 struct str cout, cin;
425 char const *cset7, *cset8, *wbot, *upper, *wend, *wcur;
426 ui32_t cset7_len, cset8_len;
427 size_t col, i, j;
428 ssize_t sz;
429 NYD_ENTER;
431 cout.s = NULL, cout.l = 0;
432 cset7 = charset_get_7bit();
433 cset7_len = (ui32_t)strlen(cset7);
434 cset8 = _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */
435 cset8_len = (ui32_t)strlen(cset8);
437 /* RFC 1468, "MIME Considerations":
438 * ISO-2022-JP may also be used in MIME Part 2 headers. The "B"
439 * encoding should be used with ISO-2022-JP text. */
440 /* TODO of course, our current implementation won't deal properly with
441 * TODO any stateful encoding at all... (the standard says each encoded
442 * TODO word must include all necessary reset sequences..., i.e., each
443 * TODO encoded word must be a self-contained iconv(3) life cycle) */
444 if (!asccasecmp(cset8, "iso-2022-jp"))
445 flags |= _NO_QP;
447 wbot = in->s;
448 upper = wbot + in->l;
449 col = sizeof("Content-Transfer-Encoding: ") -1; /* dreadful thing */
451 for (sz = 0; wbot < upper; flags &= ~_FIRST, wbot = wend) {
452 flags &= _RND_MASK;
453 wcur = wbot;
454 while (wcur < upper && whitechar(*wcur)) {
455 flags |= _SPACE;
456 ++wcur;
459 /* Any occurrence of whitespace resets prevention of lines >SHOULD via
460 * enforced encoding (xxx SHOULD, but.. encoding is expensive!!) */
461 if (flags & _SPACE)
462 flags &= ~_SHOULD_BEE;
464 /* Data ends with WS - dump it and done.
465 * Also, if we have seen multiple successive whitespace characters, then
466 * if there was no encoded word last, i.e., if we can simply take them
467 * over to the output as-is, keep one WS for possible later separation
468 * purposes and simply print the others as-is, directly! */
469 if (wcur == upper) {
470 wend = wcur;
471 goto jnoenc_putws;
473 if ((flags & (_ENC_LAST | _SPACE)) == _SPACE && wcur - wbot > 1) {
474 wend = wcur - 1;
475 goto jnoenc_putws;
478 /* Skip over a word to next non-whitespace, keep track along the way
479 * wether our 7-bit charset suffices to represent the data */
480 for (wend = wcur; wend < upper; ++wend) {
481 if (whitechar(*wend))
482 break;
483 if ((uc_i)*wend & 0x80)
484 flags |= _8BIT;
487 /* Decide wether the range has to become encoded or not */
488 i = PTR2SIZE(wend - wcur);
489 j = mime_cte_mustquote(wcur, i, MIMECTE_ISHEAD);
490 /* If it just cannot fit on a SHOULD line length, force encode */
491 if (i >= _MAXCOL) {
492 flags |= _SHOULD_BEE; /* (Sigh: SHOULD only, not MUST..) */
493 goto j_beejump;
495 if ((flags & _SHOULD_BEE) || j > 0) {
496 j_beejump:
497 flags |= _ENCODE;
498 /* Use base64 if requested or more than 50% -37.5-% of the bytes of
499 * the string need to be encoded */
500 if ((flags & _NO_QP) || j >= i >> 1)/*(i >> 2) + (i >> 3))*/
501 flags |= _ENC_B64;
503 DBG( if (flags & _8BIT) assert(flags & _ENCODE); )
505 if (!(flags & _ENCODE)) {
506 /* Encoded word produced, but no linear whitespace for necessary RFC
507 * 2047 separation? Generate artificial data (bad standard!) */
508 if ((flags & (_ENC_LAST | _SPACE)) == _ENC_LAST) {
509 if (col >= _MAXCOL) {
510 putc('\n', fo);
511 ++sz;
512 col = 0;
514 putc(' ', fo);
515 ++sz;
516 ++col;
519 jnoenc_putws:
520 flags &= ~_ENC_LAST;
522 /* todo No effort here: (1) v15.0 has to bring complete rewrite,
523 * todo (2) the standard is braindead and (3) usually this is one
524 * todo word only, and why be smarter than the standard? */
525 jnoenc_retry:
526 i = PTR2SIZE(wend - wbot);
527 if (i + col <= (flags & _OVERLONG ? MIME_LINELEN_MAX : _MAXCOL)) {
528 i = fwrite(wbot, sizeof *wbot, i, fo);
529 sz += i;
530 col += i;
531 continue;
534 /* Doesn't fit, try to break the line first; */
535 if (col > 1) {
536 putc('\n', fo);
537 if (whitechar(*wbot)) {
538 putc((uc_i)*wbot, fo);
539 ++wbot;
540 } else
541 putc(' ', fo); /* Bad standard: artificial data! */
542 sz += 2;
543 col = 1;
544 flags |= _OVERLONG;
545 goto jnoenc_retry;
548 /* It is so long that it needs to be broken, effectively causing
549 * artificial spaces to be inserted (bad standard), yuck */
550 /* todo This is not multibyte safe, as above; and completely stupid
551 * todo P.S.: our _SHOULD_BEE prevents these cases in the meanwhile */
552 wcur = wbot + MIME_LINELEN_MAX - 8;
553 while (wend > wcur)
554 wend -= 4;
555 goto jnoenc_retry;
556 } else {
557 /* Encoding to encoded word(s); deal with leading whitespace, place
558 * a separator first as necessary: encoded words must always be
559 * separated from text and other encoded words with linear WS.
560 * And if an encoded word was last, intermediate whitespace must
561 * also be encoded, otherwise it would get stripped away! */
562 wcur = UNCONST("");
563 if ((flags & (_ENC_LAST | _SPACE)) != _SPACE) {
564 /* Reinclude whitespace */
565 flags &= ~_SPACE;
566 /* We don't need to place a separator at the very beginning */
567 if (!(flags & _FIRST))
568 wcur = UNCONST(" ");
569 } else
570 wcur = wbot++;
572 flags |= _ENC_LAST;
574 /* RFC 2047:
575 * An 'encoded-word' may not be more than 75 characters long,
576 * including 'charset', 'encoding', 'encoded-text', and
577 * delimiters. If it is desirable to encode more text than will
578 * fit in an 'encoded-word' of 75 characters, multiple
579 * 'encoded-word's (separated by CRLF SPACE) may be used.
581 * While there is no limit to the length of a multiple-line
582 * header field, each line of a header field that contains one
583 * or more 'encoded-word's is limited to 76 characters */
584 jenc_retry:
585 cin.s = UNCONST(wbot);
586 cin.l = PTR2SIZE(wend - wbot);
588 if (flags & _ENC_B64)
589 j = b64_encode(&cout, &cin, B64_ISHEAD | B64_ISENCWORD)->l;
590 else
591 j = qp_encode(&cout, &cin, QP_ISHEAD | QP_ISENCWORD)->l;
592 /* (Avoid trigraphs in the RFC 2047 placeholder..) */
593 i = j + (flags & _8BIT ? cset8_len : cset7_len) + sizeof("=!!B!!=") -1;
594 if (*wcur != '\0')
595 ++i;
597 jenc_retry_same:
598 /* Unfortunately RFC 2047 explicitly disallows encoded words to be
599 * longer (just like RFC 5322's "a line SHOULD fit in 78 but MAY be
600 * 998 characters long"), so we cannot use the _OVERLONG mechanism,
601 * even though all tested mailers seem to support it */
602 if (i + col <= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ _MAXCOL)) {
603 fprintf(fo, "%.1s=?%s?%c?%.*s?=",
604 wcur, (flags & _8BIT ? cset8 : cset7),
605 (flags & _ENC_B64 ? 'B' : 'Q'),
606 (int)cout.l, cout.s);
607 sz += i;
608 col += i;
609 continue;
612 /* Doesn't fit, try to break the line first */
613 /* TODO I've commented out the _FIRST test since we (1) cannot do
614 * TODO _OVERLONG since (MUAs support but) the standard disallows,
615 * TODO and because of our iconv problem i prefer an empty first line
616 * TODO in favour of a possibly messed up multibytes character. :-( */
617 if (col > 1 /* TODO && !(flags & _FIRST)*/) {
618 putc('\n', fo);
619 sz += 2;
620 col = 1;
621 if (!(flags & _SPACE)) {
622 putc(' ', fo);
623 wcur = UNCONST("");
624 /*flags |= _OVERLONG;*/
625 goto jenc_retry_same;
626 } else {
627 putc((uc_i)*wcur, fo);
628 if (whitechar(*(wcur = wbot)))
629 ++wbot;
630 else {
631 flags &= ~_SPACE;
632 wcur = UNCONST("");
634 /*flags &= ~_OVERLONG;*/
635 goto jenc_retry;
639 /* It is so long that it needs to be broken, effectively causing
640 * artificial data to be inserted (bad standard), yuck */
641 /* todo This is not multibyte safe, as above */
642 /*if (!(flags & _OVERLONG)) {
643 flags |= _OVERLONG;
644 goto jenc_retry;
646 i = PTR2SIZE(wend - wbot) + !!(flags & _SPACE);
647 j = 3 + !(flags & _ENC_B64);
648 for (;;) {
649 wend -= j;
650 i -= j;
651 /* (Note the problem most likely is the transfer-encoding blow,
652 * which is why we test this *after* the decrements.. */
653 if (i <= _MAXCOL)
654 break;
656 goto jenc_retry;
660 if (cout.s != NULL)
661 free(cout.s);
662 NYD_LEAVE;
663 return sz;
666 static ssize_t
667 convhdra(char const *str, size_t len, FILE *fp)
669 #ifdef HAVE_ICONV
670 struct str ciconv;
671 #endif
672 struct str cin;
673 ssize_t ret = 0;
674 NYD_ENTER;
676 cin.s = UNCONST(str);
677 cin.l = len;
678 #ifdef HAVE_ICONV
679 ciconv.s = NULL;
680 if (iconvd != (iconv_t)-1) {
681 ciconv.l = 0;
682 if (n_iconv_str(iconvd, &ciconv, &cin, NULL, FAL0) != 0)
683 goto jleave;
684 cin = ciconv;
686 #endif
687 ret = mime_write_tohdr(&cin, fp);
688 #ifdef HAVE_ICONV
689 jleave:
690 if (ciconv.s != NULL)
691 free(ciconv.s);
692 #endif
693 NYD_LEAVE;
694 return ret;
697 static ssize_t
698 mime_write_tohdr_a(struct str *in, FILE *f) /* TODO error handling */
700 char const *cp, *lastcp;
701 ssize_t sz, x;
702 NYD_ENTER;
704 in->s[in->l] = '\0';
705 lastcp = in->s;
706 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
707 if ((sz = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0)
708 goto jleave;
709 lastcp = cp;
710 } else {
711 cp = in->s;
712 sz = 0;
715 for ( ; *cp != '\0'; ++cp) {
716 switch (*cp) {
717 case '(':
718 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp + 1), f);
719 lastcp = ++cp;
720 cp = skip_comment(cp);
721 if (--cp > lastcp) {
722 if ((x = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0) {
723 sz = x;
724 goto jleave;
726 sz += x;
728 lastcp = cp;
729 break;
730 case '"':
731 while (*cp) {
732 if (*++cp == '"')
733 break;
734 if (*cp == '\\' && cp[1] != '\0')
735 ++cp;
737 break;
740 if (cp > lastcp)
741 sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp), f);
742 jleave:
743 NYD_LEAVE;
744 return sz;
747 static void
748 addstr(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
750 NYD_ENTER;
751 *buf = srealloc(*buf, *sz += len);
752 memcpy(&(*buf)[*pos], str, len);
753 *pos += len;
754 NYD_LEAVE;
757 static void
758 addconv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
760 struct str in, out;
761 NYD_ENTER;
763 in.s = UNCONST(str);
764 in.l = len;
765 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
766 addstr(buf, sz, pos, out.s, out.l);
767 free(out.s);
768 NYD_LEAVE;
771 FL char const *
772 charset_get_7bit(void)
774 char const *t;
775 NYD_ENTER;
777 if ((t = ok_vlook(charset_7bit)) == NULL)
778 t = CHARSET_7BIT;
779 NYD_LEAVE;
780 return t;
783 #ifdef HAVE_ICONV
784 FL char const *
785 charset_get_8bit(void)
787 char const *t;
788 NYD_ENTER;
790 if ((t = ok_vlook(CHARSET_8BIT_OKEY)) == NULL)
791 t = CHARSET_8BIT;
792 NYD_LEAVE;
793 return t;
795 #endif
797 FL char const *
798 charset_get_lc(void)
800 char const *t;
801 NYD_ENTER;
803 if ((t = ok_vlook(ttycharset)) == NULL)
804 t = CHARSET_8BIT;
805 NYD_LEAVE;
806 return t;
809 FL bool_t
810 charset_iter_reset(char const *a_charset_to_try_first)
812 char const *sarr[3];
813 size_t sarrl[3], len;
814 char *cp;
815 NYD_ENTER;
816 UNUSED(a_charset_to_try_first);
818 #ifdef HAVE_ICONV
819 sarr[0] = a_charset_to_try_first;
820 if ((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
821 ok_blook(sendcharsets_else_ttycharset))
822 sarr[1] = charset_get_lc();
823 sarr[2] = charset_get_8bit();
824 #else
825 sarr[2] = charset_get_lc();
826 #endif
828 sarrl[2] = len = strlen(sarr[2]);
829 #ifdef HAVE_ICONV
830 if ((cp = UNCONST(sarr[1])) != NULL)
831 len += (sarrl[1] = strlen(cp));
832 else
833 sarrl[1] = 0;
834 if ((cp = UNCONST(sarr[0])) != NULL)
835 len += (sarrl[0] = strlen(cp));
836 else
837 sarrl[0] = 0;
838 #endif
840 _cs_iter_base = cp = salloc(len + 1 + 1 +1);
842 #ifdef HAVE_ICONV
843 if ((len = sarrl[0]) != 0) {
844 memcpy(cp, sarr[0], len);
845 cp[len] = ',';
846 cp += ++len;
848 if ((len = sarrl[1]) != 0) {
849 memcpy(cp, sarr[1], len);
850 cp[len] = ',';
851 cp += ++len;
853 #endif
854 len = sarrl[2];
855 memcpy(cp, sarr[2], len);
856 cp[len] = '\0';
858 _CS_ITER_STEP();
859 NYD_LEAVE;
860 return (_cs_iter != NULL);
863 FL bool_t
864 charset_iter_next(void)
866 bool_t rv;
867 NYD_ENTER;
869 _CS_ITER_STEP();
870 rv = (_cs_iter != NULL);
871 NYD_LEAVE;
872 return rv;
875 FL bool_t
876 charset_iter_is_valid(void)
878 bool_t rv;
879 NYD_ENTER;
881 rv = (_cs_iter != NULL);
882 NYD_LEAVE;
883 return rv;
886 FL char const *
887 charset_iter(void)
889 char const *rv;
890 NYD_ENTER;
892 rv = _cs_iter;
893 NYD_LEAVE;
894 return rv;
897 FL void
898 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
900 NYD_ENTER;
901 outer_storage[0] = _cs_iter_base;
902 outer_storage[1] = _cs_iter;
903 NYD_LEAVE;
906 FL void
907 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
909 NYD_ENTER;
910 _cs_iter_base = outer_storage[0];
911 _cs_iter = outer_storage[1];
912 NYD_LEAVE;
915 #ifdef HAVE_ICONV
916 FL char const *
917 need_hdrconv(struct header *hp, enum gfield w) /* TODO once only, then iter */
919 char const *ret = NULL;
920 NYD_ENTER;
922 if (w & GIDENT) {
923 if (hp->h_from != NULL) {
924 if (_name_highbit(hp->h_from))
925 goto jneeds;
926 } else if (_has_highbit(myaddrs(NULL)))
927 goto jneeds;
928 if (hp->h_organization) {
929 if (_has_highbit(hp->h_organization))
930 goto jneeds;
931 } else if (_has_highbit(ok_vlook(ORGANIZATION)))
932 goto jneeds;
933 if (hp->h_replyto) {
934 if (_name_highbit(hp->h_replyto))
935 goto jneeds;
936 } else if (_has_highbit(ok_vlook(replyto)))
937 goto jneeds;
938 if (hp->h_sender) {
939 if (_name_highbit(hp->h_sender))
940 goto jneeds;
941 } else if (_has_highbit(ok_vlook(sender)))
942 goto jneeds;
944 if ((w & GTO) && _name_highbit(hp->h_to))
945 goto jneeds;
946 if ((w & GCC) && _name_highbit(hp->h_cc))
947 goto jneeds;
948 if ((w & GBCC) && _name_highbit(hp->h_bcc))
949 goto jneeds;
950 if ((w & GSUBJECT) && _has_highbit(hp->h_subject))
951 jneeds:
952 ret = _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
953 NYD_LEAVE;
954 return ret;
956 #endif /* HAVE_ICONV */
958 FL enum mimeenc
959 mime_getenc(char *p)
961 enum mimeenc rv;
962 NYD_ENTER;
964 if (is_this_enc(p, "7bit"))
965 rv = MIME_7B;
966 else if (is_this_enc(p, "8bit"))
967 rv = MIME_8B;
968 else if (is_this_enc(p, "base64"))
969 rv = MIME_B64;
970 else if (is_this_enc(p, "binary"))
971 rv = MIME_BIN;
972 else if (is_this_enc(p, "quoted-printable"))
973 rv = MIME_QP;
974 else
975 rv = MIME_NONE;
976 NYD_LEAVE;
977 return rv;
980 FL char *
981 mime_getparam(char const *param, char *h)
983 char *p = h, *q, *rv = NULL;
984 int c;
985 size_t sz;
986 NYD_ENTER;
988 sz = strlen(param);
989 if (!whitechar(*p)) {
990 c = '\0';
991 while (*p && (*p != ';' || c == '\\')) {
992 c = (c == '\\') ? '\0' : *p;
993 ++p;
995 if (*p++ == '\0')
996 goto jleave;
999 for (;;) {
1000 while (whitechar(*p))
1001 ++p;
1002 if (!ascncasecmp(p, param, sz)) {
1003 p += sz;
1004 while (whitechar(*p))
1005 ++p;
1006 if (*p++ == '=')
1007 break;
1009 c = '\0';
1010 while (*p != '\0' && (*p != ';' || c == '\\')) {
1011 if (*p == '"' && c != '\\') {
1012 ++p;
1013 while (*p != '\0' && (*p != '"' || c == '\\')) {
1014 c = (c == '\\') ? '\0' : *p;
1015 ++p;
1017 ++p;
1018 } else {
1019 c = (c == '\\') ? '\0' : *p;
1020 ++p;
1023 if (*p++ == '\0')
1024 goto jleave;
1026 while (whitechar(*p))
1027 ++p;
1029 q = p;
1030 if (*p == '"') {
1031 p++;
1032 if ((q = strchr(p, '"')) == NULL)
1033 goto jleave;
1034 } else {
1035 while (*q != '\0' && !whitechar(*q) && *q != ';')
1036 ++q;
1038 sz = PTR2SIZE(q - p);
1039 rv = salloc(q - p +1);
1040 memcpy(rv, p, sz);
1041 rv[sz] = '\0';
1042 jleave:
1043 NYD_LEAVE;
1044 return rv;
1047 FL char *
1048 mime_get_boundary(char *h, size_t *len)
1050 char *q = NULL, *p;
1051 size_t sz;
1052 NYD_ENTER;
1054 if ((p = mime_getparam("boundary", h)) != NULL) {
1055 sz = strlen(p);
1056 if (len != NULL)
1057 *len = sz + 2;
1058 q = salloc(sz + 2 +1);
1059 q[0] = q[1] = '-';
1060 memcpy(q + 2, p, sz);
1061 *(q + sz + 2) = '\0';
1063 NYD_LEAVE;
1064 return q;
1067 FL char *
1068 mime_create_boundary(void)
1070 char *bp;
1071 NYD_ENTER;
1073 bp = salloc(48);
1074 snprintf(bp, 48, "=_%011" PRIu64 "=-%s=_",
1075 (ui64_t)time_current.tc_time, getrandstring(47 - (11 + 6)));
1076 NYD_LEAVE;
1077 return bp;
1080 FL int
1081 mime_classify_file(FILE *fp, char const **contenttype, char const **charset,
1082 int *do_iconv)
1084 /* TODO classify once only PLEASE PLEASE PLEASE */
1085 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
1086 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
1087 * TODO and report that state to the outer world */
1088 #define F_ "From "
1089 #define F_SIZEOF (sizeof(F_) -1)
1091 char f_buf[F_SIZEOF], *f_p = f_buf;
1092 enum {
1093 _CLEAN = 0, /* Plain RFC 2822 message */
1094 _NCTT = 1<<0, /* *contenttype == NULL */
1095 _ISTXT = 1<<1, /* *contenttype =~ text/ */
1096 _ISTXTCOK = 1<<2, /* _ISTXT + *mime-allow-text-controls* */
1097 _HIGHBIT = 1<<3, /* Not 7bit clean */
1098 _LONGLINES = 1<<4, /* MIME_LINELEN_LIMIT exceed. */
1099 _CTRLCHAR = 1<<5, /* Control characters seen */
1100 _HASNUL = 1<<6, /* Contains \0 characters */
1101 _NOTERMNL = 1<<7, /* Lacks a final newline */
1102 _TRAILWS = 1<<8, /* Blanks before NL */
1103 _FROM_ = 1<<9 /* ^From_ seen */
1104 } ctt = _CLEAN;
1105 enum conversion convert;
1106 ssize_t curlen;
1107 int c, lastc;
1108 NYD_ENTER;
1110 assert(ftell(fp) == 0x0l);
1112 *do_iconv = 0;
1114 if (*contenttype == NULL)
1115 ctt = _NCTT;
1116 else if (!ascncasecmp(*contenttype, "text/", 5))
1117 ctt = ok_blook(mime_allow_text_controls) ? _ISTXT | _ISTXTCOK : _ISTXT;
1118 convert = _conversion_by_encoding();
1120 if (fsize(fp) == 0)
1121 goto j7bit;
1123 /* We have to inspect the file content */
1124 for (curlen = 0, c = EOF;; ++curlen) {
1125 lastc = c;
1126 c = getc(fp);
1128 if (c == '\0') {
1129 ctt |= _HASNUL;
1130 if (!(ctt & _ISTXTCOK))
1131 break;
1132 continue;
1134 if (c == '\n' || c == EOF) {
1135 if (curlen >= MIME_LINELEN_LIMIT)
1136 ctt |= _LONGLINES;
1137 if (c == EOF)
1138 break;
1139 if (blankchar(lastc))
1140 ctt |= _TRAILWS;
1141 f_p = f_buf;
1142 curlen = -1;
1143 continue;
1145 /* A bit hairy is handling of \r=\x0D=CR.
1146 * RFC 2045, 6.7:
1147 * Control characters other than TAB, or CR and LF as parts of CRLF
1148 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
1149 * we cannot peek the next character. Thus right here, inspect the last
1150 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
1151 /*else*/ if (lastc == '\r')
1152 ctt |= _CTRLCHAR;
1154 /* Control character? XXX this is all ASCII here */
1155 if (c < 0x20 || c == 0x7F) {
1156 /* RFC 2045, 6.7, as above ... */
1157 if (c != '\t' && c != '\r')
1158 ctt |= _CTRLCHAR;
1159 /* If there is a escape sequence in backslash notation defined for
1160 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
1161 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
1162 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
1163 * \e=\x1B=ESC */
1164 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
1165 continue;
1166 ctt |= _HASNUL; /* Force base64 */
1167 if (!(ctt & _ISTXTCOK))
1168 break;
1169 } else if ((ui8_t)c & 0x80) {
1170 ctt |= _HIGHBIT;
1171 /* TODO count chars with HIGHBIT? libmagic?
1172 * TODO try encode part - base64 if bails? */
1173 if (!(ctt & (_NCTT | _ISTXT))) { /* TODO _NCTT?? */
1174 ctt |= _HASNUL; /* Force base64 */
1175 break;
1177 } else if (!(ctt & _FROM_) && UICMP(z, curlen, <, F_SIZEOF)) {
1178 *f_p++ = (char)c;
1179 if (UICMP(z, curlen, ==, F_SIZEOF - 1) &&
1180 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
1181 !memcmp(f_buf, F_, F_SIZEOF))
1182 ctt |= _FROM_;
1185 if (lastc != '\n')
1186 ctt |= _NOTERMNL;
1187 rewind(fp);
1189 if (ctt & _HASNUL) {
1190 convert = CONV_TOB64;
1191 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1192 * on request; else enforce what file(1)/libmagic(3) would suggest */
1193 if (ctt & _ISTXTCOK)
1194 goto jcharset;
1195 if (ctt & (_NCTT | _ISTXT))
1196 *contenttype = "application/octet-stream";
1197 if (*charset == NULL)
1198 *charset = "binary";
1199 goto jleave;
1202 if (ctt & (_LONGLINES | _CTRLCHAR | _NOTERMNL | _TRAILWS | _FROM_)) {
1203 if (convert != CONV_TOB64)
1204 convert = CONV_TOQP;
1205 goto jstepi;
1207 if (ctt & _HIGHBIT) {
1208 jstepi:
1209 if (ctt & (_NCTT | _ISTXT))
1210 *do_iconv = ((ctt & _HIGHBIT) != 0);
1211 } else
1212 j7bit:
1213 convert = CONV_7BIT;
1214 if (ctt & _NCTT)
1215 *contenttype = "text/plain";
1217 /* Not an attachment with specified charset? */
1218 jcharset:
1219 if (*charset == NULL) /* TODO MIME/send: iter active? iter! else */
1220 *charset = (ctt & _HIGHBIT) ? _CS_ITER_GET() : charset_get_7bit();
1221 jleave:
1222 NYD_LEAVE;
1223 return convert;
1224 #undef F_
1225 #undef F_SIZEOF
1228 FL enum mimecontent
1229 mime_classify_content_of_part(struct mimepart *mpp)
1231 enum mimecontent mc;
1232 char const *ct;
1233 union {char const *cp; long l;} mce;
1234 NYD_ENTER;
1236 mc = MIME_UNKNOWN;
1237 ct = mpp->m_ct_type_plain;
1239 if (!asccasecmp(ct, "application/octet-stream") && mpp->m_filename != NULL &&
1240 (mce.cp = ok_vlook(mime_counter_evidence)) != NULL) {
1241 ct = mime_classify_content_type_by_fileext(mpp->m_filename);
1242 if (ct == NULL)
1243 /* TODO add bit 1 to possible *mime-counter-evidence* value
1244 * TODO and let it mean to save the attachment in
1245 * TODO a temporary file that mime_classify_file() can
1246 * TODO examine, and using MIME_TEXT if that gives us
1247 * TODO something that seems to be human readable?! */
1248 goto jleave;
1250 mce.l = strtol(mce.cp, NULL, 0);
1251 if (mce.l & MIMECE_USR_OVWR)
1252 mpp->m_ct_type_usr_ovwr = UNCONST(ct);
1255 if (strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
1256 mc = MIME_TEXT;
1257 else if (!asccasecmp(ct, "text/plain"))
1258 mc = MIME_TEXT_PLAIN;
1259 else if (!asccasecmp(ct, "text/html"))
1260 mc = MIME_TEXT_HTML;
1261 else if (!ascncasecmp(ct, "text/", 5))
1262 mc = MIME_TEXT;
1263 else if (!asccasecmp(ct, "message/rfc822"))
1264 mc = MIME_822;
1265 else if (!ascncasecmp(ct, "message/", 8))
1266 mc = MIME_MESSAGE;
1267 else if (!asccasecmp(ct, "multipart/alternative"))
1268 mc = MIME_ALTERNATIVE;
1269 else if (!asccasecmp(ct, "multipart/digest"))
1270 mc = MIME_DIGEST;
1271 else if (!ascncasecmp(ct, "multipart/", 10))
1272 mc = MIME_MULTI;
1273 else if (!asccasecmp(ct, "application/x-pkcs7-mime") ||
1274 !asccasecmp(ct, "application/pkcs7-mime"))
1275 mc = MIME_PKCS7;
1276 jleave:
1277 NYD_LEAVE;
1278 return mc;
1281 FL char *
1282 mime_classify_content_type_by_fileext(char const *name)
1284 char *content = NULL;
1285 struct mtnode *mtn;
1286 size_t nlen;
1287 NYD_ENTER;
1289 /* TODO mime_classify(): mime.types(5) has *-gz but we search dot!
1290 * TODO i.e., we cannot handle files like dubidu.tar-gz; need globs! */
1291 /* TODO even better: regex, with fast lists for (README|INSTALL|NEWS) etc,
1292 * TODO that, also add some mechanism for filenames without extension */
1293 if ((name = strrchr(name, '.')) == NULL || *++name == '\0')
1294 goto jleave;
1296 if (_mt_list == NULL)
1297 _mt_init();
1298 if (NELEM(_mt_bltin) == 0 && _mt_list == (struct mtnode*)-1)
1299 goto jleave;
1301 nlen = strlen(name);
1302 for (mtn = _mt_list; mtn != NULL; mtn = mtn->mt_next) {
1303 char const *ext = mtn->mt_line + mtn->mt_mtlen + 1,
1304 *cp = ext;
1305 do {
1306 while (!whitechar(*cp) && *cp != '\0')
1307 ++cp;
1308 /* Better to do case-insensitive comparison on extension, since the
1309 * RFC doesn't specify case of attribute values? */
1310 if (nlen == PTR2SIZE(cp - ext) && !ascncasecmp(name, ext, nlen)) {
1311 content = savestrbuf(mtn->mt_line, mtn->mt_mtlen);
1312 goto jleave;
1314 while (whitechar(*cp) && *cp != '\0')
1315 ++cp;
1316 ext = cp;
1317 } while (*ext != '\0');
1319 jleave:
1320 NYD_LEAVE;
1321 return content;
1324 FL char *
1325 mimepart_get_handler(struct mimepart const *mpp)
1327 #define __S "pipe-"
1328 #define __L (sizeof(__S) -1)
1329 char const *es, *cs;
1330 size_t el, cl, l;
1331 char *buf, *rv;
1332 NYD_ENTER;
1334 /* TODO some mechanism for filenames without extension */
1335 el = ((es = mpp->m_filename) != NULL && (es = strrchr(es, '.')) != NULL &&
1336 *++es != '\0') ? strlen(es) : 0;
1337 cl = ((cs = mpp->m_ct_type_usr_ovwr) != NULL ||
1338 (cs = mpp->m_ct_type_plain) != NULL) ? strlen(cs) : 0;
1339 if ((l = MAX(el, cl)) == 0) {
1340 rv = NULL;
1341 goto jleave;
1344 buf = ac_alloc(__L + l +1);
1345 memcpy(buf, __S, __L);
1347 /* File-extension handlers take precedence.
1348 * Yes, we really "fail" here for file extensions which clash MIME types */
1349 if (el > 0) {
1350 memcpy(buf + __L, es, el +1);
1351 for (rv = buf + __L; *rv != '\0'; ++rv)
1352 *rv = lowerconv(*rv);
1354 if ((rv = vok_vlook(buf)) != NULL)
1355 goto jok;
1358 /* Then MIME Content-Type: */
1359 if (cl > 0) {
1360 memcpy(buf + __L, cs, cl +1);
1361 for (rv = buf + __L; *rv != '\0'; ++rv)
1362 *rv = lowerconv(*rv);
1364 if ((rv = vok_vlook(buf)) != NULL)
1365 goto jok;
1368 rv = NULL;
1369 jok:
1370 ac_free(buf);
1371 jleave:
1372 NYD_LEAVE;
1373 return rv;
1374 #undef __L
1375 #undef __S
1378 FL int
1379 c_mimetypes(void *v)
1381 char **argv = v;
1382 struct mtnode *mtn;
1383 NYD_ENTER;
1385 if (*argv == NULL)
1386 goto jlist;
1387 if (argv[1] != NULL)
1388 goto jerr;
1389 if (!asccasecmp(*argv, "show"))
1390 goto jlist;
1391 if (!asccasecmp(*argv, "clear"))
1392 goto jclear;
1393 jerr:
1394 fprintf(stderr, "Synopsis: mimetypes: %s\n",
1395 _("Either <show> (default) or <clear> the mime.types cache"));
1396 v = NULL;
1397 jleave:
1398 NYD_LEAVE;
1399 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1401 jlist: {
1402 FILE *fp;
1403 size_t l;
1405 if (_mt_list == NULL)
1406 _mt_init();
1407 if (NELEM(_mt_bltin) == 0 && _mt_list == (struct mtnode*)-1) {
1408 fprintf(stderr, _("Interpolate what file?\n"));
1409 v = NULL;
1410 goto jleave;
1413 if ((fp = Ftmp(NULL, "mimelist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
1414 NULL) {
1415 perror("tmpfile");
1416 v = NULL;
1417 goto jleave;
1420 for (l = 0, mtn = _mt_list; mtn != NULL; ++l, mtn = mtn->mt_next)
1421 fprintf(fp, "%s\t%s\n", mtn->mt_line, mtn->mt_line + mtn->mt_mtlen + 1);
1423 page_or_print(fp, l);
1424 Fclose(fp);
1426 goto jleave;
1428 jclear:
1429 if (NELEM(_mt_bltin) == 0 && _mt_list == (struct mtnode*)-1)
1430 _mt_list = NULL;
1431 while ((mtn = _mt_list) != NULL) {
1432 _mt_list = mtn->mt_next;
1433 free(mtn);
1435 goto jleave;
1438 FL void
1439 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
1441 /* TODO mime_fromhdr(): is called with strings that contain newlines;
1442 * TODO this is the usual newline problem all around the codebase;
1443 * TODO i.e., if we strip it, then the display misses it ;>
1444 * TODO this is why it is so messy and why S-nail v14.2 plus additional
1445 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
1446 * TODO why our display reflects what is contained in the message: the 1:1
1447 * TODO relationship of message content and display!
1448 * TODO instead a header line should be decoded to what it is (a single
1449 * TODO line that is) and it should be objective to the backend wether
1450 * TODO it'll be folded to fit onto the display or not, e.g., for search
1451 * TODO purposes etc. then the only condition we have to honour in here
1452 * TODO is that whitespace in between multiple adjacent MIME encoded words
1453 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
1454 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
1455 struct str cin, cout;
1456 char *p, *op, *upper, *cs, *cbeg;
1457 ui32_t convert, lastenc, lastoutl;
1458 #ifdef HAVE_ICONV
1459 char const *tcs;
1460 iconv_t fhicd = (iconv_t)-1;
1461 #endif
1462 NYD_ENTER;
1464 out->l = 0;
1465 if (in->l == 0) {
1466 *(out->s = smalloc(1)) = '\0';
1467 goto jleave;
1469 out->s = NULL;
1471 #ifdef HAVE_ICONV
1472 tcs = charset_get_lc();
1473 #endif
1474 p = in->s;
1475 upper = p + in->l;
1476 lastenc = lastoutl = 0;
1478 while (p < upper) {
1479 op = p;
1480 if (*p == '=' && *(p + 1) == '?') {
1481 p += 2;
1482 cbeg = p;
1483 while (p < upper && *p != '?')
1484 ++p; /* strip charset */
1485 if (p >= upper)
1486 goto jnotmime;
1487 cs = salloc(PTR2SIZE(++p - cbeg));
1488 memcpy(cs, cbeg, PTR2SIZE(p - cbeg - 1));
1489 cs[p - cbeg - 1] = '\0';
1490 #ifdef HAVE_ICONV
1491 if (fhicd != (iconv_t)-1)
1492 n_iconv_close(fhicd);
1493 fhicd = asccasecmp(cs, tcs) ? n_iconv_open(tcs, cs) : (iconv_t)-1;
1494 #endif
1495 switch (*p) {
1496 case 'B': case 'b':
1497 convert = CONV_FROMB64;
1498 break;
1499 case 'Q': case 'q':
1500 convert = CONV_FROMQP;
1501 break;
1502 default: /* invalid, ignore */
1503 goto jnotmime;
1505 if (*++p != '?')
1506 goto jnotmime;
1507 cin.s = ++p;
1508 cin.l = 1;
1509 for (;;) {
1510 if (PTRCMP(p + 1, >=, upper))
1511 goto jnotmime;
1512 if (*p++ == '?' && *p == '=')
1513 break;
1514 ++cin.l;
1516 ++p;
1517 --cin.l;
1519 cout.s = NULL;
1520 cout.l = 0;
1521 if (convert == CONV_FROMB64) {
1522 /* XXX Take care for, and strip LF from
1523 * XXX [Invalid Base64 encoding ignored] */
1524 if (b64_decode(&cout, &cin, NULL) == STOP &&
1525 cout.s[cout.l - 1] == '\n')
1526 --cout.l;
1527 } else
1528 qp_decode(&cout, &cin, NULL);
1530 out->l = lastenc;
1531 #ifdef HAVE_ICONV
1532 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
1533 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
1534 convert = n_iconv_str(fhicd, &cin, &cout, NULL, TRU1);
1535 out = n_str_add(out, &cin);
1536 if (convert) /* EINVAL at EOS */
1537 out = n_str_add_buf(out, "?", 1);
1538 free(cin.s);
1539 } else
1540 #endif
1541 out = n_str_add(out, &cout);
1542 lastenc = lastoutl = out->l;
1543 free(cout.s);
1544 } else
1545 jnotmime: {
1546 bool_t onlyws;
1548 p = op;
1549 onlyws = (lastenc > 0);
1550 for (;;) {
1551 if (++op == upper)
1552 break;
1553 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
1554 break;
1555 if (onlyws && !blankchar(*op))
1556 onlyws = FAL0;
1559 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
1560 p = op;
1561 if (!onlyws || lastoutl != lastenc)
1562 lastenc = out->l;
1563 lastoutl = out->l;
1566 out->s[out->l] = '\0';
1568 if (flags & TD_ISPR) {
1569 makeprint(out, &cout);
1570 free(out->s);
1571 *out = cout;
1573 if (flags & TD_DELCTRL)
1574 out->l = delctrl(out->s, out->l);
1575 #ifdef HAVE_ICONV
1576 if (fhicd != (iconv_t)-1)
1577 n_iconv_close(fhicd);
1578 #endif
1579 jleave:
1580 NYD_LEAVE;
1581 return;
1584 FL char *
1585 mime_fromaddr(char const *name)
1587 char const *cp, *lastcp;
1588 char *res = NULL;
1589 size_t ressz = 1, rescur = 0;
1590 NYD_ENTER;
1592 if (name == NULL)
1593 goto jleave;
1594 if (*name == '\0') {
1595 res = savestr(name);
1596 goto jleave;
1599 if ((cp = routeaddr(name)) != NULL && cp > name) {
1600 addconv(&res, &ressz, &rescur, name, PTR2SIZE(cp - name));
1601 lastcp = cp;
1602 } else
1603 cp = lastcp = name;
1605 for ( ; *cp; ++cp) {
1606 switch (*cp) {
1607 case '(':
1608 addstr(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp + 1));
1609 lastcp = ++cp;
1610 cp = skip_comment(cp);
1611 if (--cp > lastcp)
1612 addconv(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1613 lastcp = cp;
1614 break;
1615 case '"':
1616 while (*cp) {
1617 if (*++cp == '"')
1618 break;
1619 if (*cp == '\\' && cp[1] != '\0')
1620 ++cp;
1622 break;
1625 if (cp > lastcp)
1626 addstr(&res, &ressz, &rescur, lastcp, PTR2SIZE(cp - lastcp));
1627 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1628 if (rescur == 0)
1629 res = UNCONST("");
1630 else
1631 res[rescur] = '\0';
1632 { char *x = res;
1633 res = savestr(res);
1634 free(x);
1636 jleave:
1637 NYD_LEAVE;
1638 return res;
1641 FL ssize_t
1642 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1643 enum tdflags dflags, struct str *rest)
1645 ssize_t rv;
1646 struct quoteflt *qf;
1647 NYD_ENTER;
1649 quoteflt_reset(qf = quoteflt_dummy(), f);
1650 rv = mime_write(ptr, size, f, convert, dflags, qf, rest);
1651 assert(quoteflt_flush(qf) == 0);
1652 NYD_LEAVE;
1653 return rv;
1656 FL ssize_t
1657 mime_write(char const *ptr, size_t size, FILE *f,
1658 enum conversion convert, enum tdflags dflags,
1659 struct quoteflt *qf, struct str *rest)
1661 /* TODO note: after send/MIME layer rewrite we will have a string pool
1662 * TODO so that memory allocation count drops down massively; for now,
1663 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1664 struct str in, out;
1665 ssize_t sz;
1666 int state;
1667 NYD_ENTER;
1669 in.s = UNCONST(ptr);
1670 in.l = size;
1671 out.s = NULL;
1672 out.l = 0;
1674 dflags |= _TD_BUFCOPY;
1675 if ((sz = size) == 0) {
1676 if (rest != NULL && rest->l != 0)
1677 goto jconvert;
1678 goto jleave;
1681 #ifdef HAVE_ICONV
1682 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1683 (convert == CONV_TOQP || convert == CONV_8BIT ||
1684 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1685 if (n_iconv_str(iconvd, &out, &in, NULL, FAL0) != 0) {
1686 /* XXX report conversion error? */;
1687 sz = -1;
1688 goto jleave;
1690 in = out;
1691 out.s = NULL;
1692 dflags &= ~_TD_BUFCOPY;
1694 #endif
1696 jconvert:
1697 switch (convert) {
1698 case CONV_FROMQP:
1699 state = qp_decode(&out, &in, rest);
1700 goto jqpb64_dec;
1701 case CONV_TOQP:
1702 qp_encode(&out, &in, QP_NONE);
1703 goto jqpb64_enc;
1704 case CONV_8BIT:
1705 sz = quoteflt_push(qf, in.s, in.l);
1706 break;
1707 case CONV_FROMB64:
1708 rest = NULL;
1709 /* FALLTHRU */
1710 case CONV_FROMB64_T:
1711 state = b64_decode(&out, &in, rest);
1712 jqpb64_dec:
1713 if ((sz = out.l) != 0) {
1714 ui32_t opl = qf->qf_pfix_len;
1715 if (state != OKAY)
1716 qf->qf_pfix_len = 0;
1717 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), rest, qf);
1718 qf->qf_pfix_len = opl;
1720 if (state != OKAY)
1721 sz = -1;
1722 break;
1723 case CONV_TOB64:
1724 b64_encode(&out, &in, B64_LF | B64_MULTILINE);
1725 jqpb64_enc:
1726 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1727 if (sz != (ssize_t)out.l)
1728 sz = -1;
1729 break;
1730 case CONV_FROMHDR:
1731 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1732 sz = quoteflt_push(qf, out.s, out.l);
1733 break;
1734 case CONV_TOHDR:
1735 sz = mime_write_tohdr(&in, f);
1736 break;
1737 case CONV_TOHDR_A:
1738 sz = mime_write_tohdr_a(&in, f);
1739 break;
1740 default:
1741 sz = _fwrite_td(&in, dflags, NULL, qf);
1742 break;
1744 jleave:
1745 if (out.s != NULL)
1746 free(out.s);
1747 if (in.s != ptr)
1748 free(in.s);
1749 NYD_LEAVE;
1750 return sz;
1753 /* s-it-mode */