Rename hfield() to hfieldX(), add hfield1()..
[s-mailx.git] / mime.c
blob722c1b5bfee4bd47d5723f815a344d739bb70f6a
1 /*
2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
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 #include "rcv.h"
41 #include "extern.h"
42 #include <ctype.h>
43 #include <errno.h>
44 #ifdef HAVE_WCTYPE_H
45 #include <wctype.h>
46 #endif /* HAVE_WCTYPE_H */
49 * Mail -- a mail program
51 * MIME support functions.
55 * You won't guess what these are for.
57 static const char basetable[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
58 static char *mimetypes_world = "/etc/mime.types";
59 static char *mimetypes_user = "~/.mime.types";
60 char *us_ascii = "us-ascii";
62 static int mustquote_body(int c);
63 static int mustquote_hdr(const char *cp, int wordstart, int wordend);
64 static int mustquote_inhdrq(int c);
65 static size_t delctrl(char *cp, size_t sz);
66 static char *getcharset(int isclean);
67 static int has_highbit(register const char *s);
68 #ifdef HAVE_ICONV
69 static void uppercopy(char *dest, const char *src);
70 static void stripdash(char *p);
71 static void invalid_seq(int c);
72 #endif /* HAVE_ICONV */
73 static int is_this_enc(const char *line, const char *encoding);
74 static char *mime_tline(char *x, char *l);
75 static char *mime_type(char *ext, char *filename);
76 static enum mimeclean mime_isclean(FILE *f);
77 static enum conversion gettextconversion(void);
78 static char *ctohex(int c, char *hex);
79 static size_t mime_write_toqp(struct str *in, FILE *fo, int (*mustquote)(int));
80 static void mime_str_toqp(struct str *in, struct str *out,
81 int (*mustquote)(int), int inhdr);
82 static void mime_fromqp(struct str *in, struct str *out, int ishdr);
83 static size_t mime_write_tohdr(struct str *in, FILE *fo);
84 static size_t convhdra(char *str, size_t len, FILE *fp);
85 static size_t mime_write_tohdr_a(struct str *in, FILE *f);
86 static void addstr(char **buf, size_t *sz, size_t *pos, char *str, size_t len);
87 static void addconv(char **buf, size_t *sz, size_t *pos, char *str, size_t len);
88 static size_t fwrite_td(void *ptr, size_t size, size_t nmemb, FILE *f,
89 enum tdflags flags, char *prefix, size_t prefixlen);
92 * Check if c must be quoted inside a message's body.
94 static int
95 mustquote_body(int c)
97 if (c != '\n' && (c < 040 || c == '=' || c >= 0177))
98 return 1;
99 return 0;
103 * Check if c must be quoted inside a message's header.
105 static int
106 mustquote_hdr(const char *cp, int wordstart, int wordend)
108 int c = *cp & 0377;
110 if (c != '\n' && (c < 040 || c >= 0177))
111 return 1;
112 if (wordstart && cp[0] == '=' && cp[1] == '?')
113 return 1;
114 if (cp[0] == '?' && cp[1] == '=' &&
115 (wordend || cp[2] == '\0' || whitechar(cp[2]&0377)))
116 return 1;
117 return 0;
121 * Check if c must be quoted inside a quoting in a message's header.
123 static int
124 mustquote_inhdrq(int c)
126 if (c != '\n'
127 && (c <= 040 || c == '=' || c == '?' || c == '_' || c >= 0177))
128 return 1;
129 return 0;
132 static size_t
133 delctrl(char *cp, size_t sz)
135 size_t x = 0, y = 0;
137 while (x < sz) {
138 if (!cntrlchar(cp[x]&0377))
139 cp[y++] = cp[x];
140 x++;
142 return y;
146 * Check if a name's address part contains invalid characters.
148 int
149 mime_name_invalid(struct name *np, int putmsg)
151 char *name = np->n_name, *addr, *p;
152 int in_quote = 0, in_domain = 0, err = 0, hadat = 0;
154 if (np->n_flags & NAME_MIME_CHECKED)
155 return (np->n_flags & NAME_MIME_INVALID) != 0;
156 np->n_flags |= NAME_MIME_CHECKED;
158 if (is_fileaddr(name))
159 return 0;
160 addr = skin(name);
162 if (addr == NULL || *addr == '\0') {
163 np->n_flags |= NAME_MIME_INVALID;
164 return 1;
166 for (p = addr; *p != '\0'; p++) {
167 if (*p == '\"') {
168 in_quote = !in_quote;
169 } else if (*p < 040 || (*p & 0377) >= 0177) {
170 err = *p & 0377;
171 break;
172 } else if (in_domain == 2) {
173 if ((*p == ']' && p[1] != '\0') || *p == '\0'
174 || *p == '\\' || whitechar(*p & 0377)) {
175 err = *p & 0377;
176 break;
178 } else if (in_quote && in_domain == 0) {
179 /*EMPTY*/;
180 } else if (*p == '\\' && p[1] != '\0') {
181 p++;
182 } else if (*p == '@') {
183 if (hadat++) {
184 if (putmsg) {
185 fprintf(stderr, catgets(catd, CATSET,
186 142,
187 "%s contains invalid @@ sequence\n"),
188 addr);
189 putmsg = 0;
191 err = *p;
192 break;
194 if (p[1] == '[')
195 in_domain = 2;
196 else
197 in_domain = 1;
198 continue;
199 } else if (*p == '(' || *p == ')' || *p == '<' || *p == '>'
200 || *p == ',' || *p == ';' || *p == ':'
201 || *p == '\\' || *p == '[' || *p == ']') {
202 err = *p & 0377;
203 break;
205 hadat = 0;
207 if (err) {
208 np->n_flags |= NAME_MIME_INVALID;
209 if (putmsg) {
210 char ce[sizeof(void*)];
211 /* 2012-09-25: dropped isprint(3) even if available
212 * since that fails for UTF-8 anyway */
213 if (err >= 040 && err <= 0177)
214 ce[0] = (char)err, ce[1] = '\0';
215 else
216 snprintf(ce, sizeof(ce), "\\%03o", err);
217 fprintf(stderr,
218 tr(143, "%s contains invalid character '%s'\n"),
219 addr, ce);
222 return err;
226 * Check all addresses in np and delete invalid ones.
228 struct name *
229 checkaddrs(struct name *np)
231 struct name *n = np;
233 while (n != NULL) {
234 if (mime_name_invalid(n, 1)) {
235 if (n->n_blink)
236 n->n_blink->n_flink = n->n_flink;
237 if (n->n_flink)
238 n->n_flink->n_blink = n->n_blink;
239 if (n == np)
240 np = n->n_flink;
242 n = n->n_flink;
244 return np;
247 static char defcharset[] = "utf-8";
250 * Get the character set dependant on the conversion.
252 static char *
253 getcharset(int isclean)
255 char *charset;
257 if (isclean & (MIME_CTRLCHAR|MIME_HASNUL))
258 charset = NULL;
259 else if (isclean & MIME_HIGHBIT) {
260 charset = (wantcharset && wantcharset != (char *)-1) ?
261 wantcharset : value("charset");
262 if (charset == NULL) {
263 charset = defcharset;
265 } else {
267 * This variable shall remain undocumented because
268 * only experts should change it.
270 charset = value("charset7");
271 if (charset == NULL) {
272 charset = us_ascii;
275 return charset;
279 * Get the setting of the terminal's character set.
281 char *
282 gettcharset(void)
284 char *t;
286 if ((t = value("ttycharset")) == NULL)
287 if ((t = value("charset")) == NULL)
288 t = defcharset;
289 return t;
292 static int
293 has_highbit(const char *s)
295 if (s) {
297 if (*s & 0200)
298 return 1;
299 while (*s++ != '\0');
301 return 0;
304 static int
305 name_highbit(struct name *np)
307 while (np) {
308 if (has_highbit(np->n_name) || has_highbit(np->n_fullname))
309 return 1;
310 np = np->n_flink;
312 return 0;
315 char *
316 need_hdrconv(struct header *hp, enum gfield w)
318 if (w & GIDENT) {
319 if (hp->h_from) {
320 if (name_highbit(hp->h_from))
321 goto needs;
322 } else if (has_highbit(myaddrs(hp)))
323 goto needs;
324 if (hp->h_organization) {
325 if (has_highbit(hp->h_organization))
326 goto needs;
327 } else if (has_highbit(value("ORGANIZATION")))
328 goto needs;
329 if (hp->h_replyto) {
330 if (name_highbit(hp->h_replyto))
331 goto needs;
332 } else if (has_highbit(value("replyto")))
333 goto needs;
334 if (hp->h_sender) {
335 if (name_highbit(hp->h_sender))
336 goto needs;
337 } else if (has_highbit(value("sender")))
338 goto needs;
340 if (w & GTO && name_highbit(hp->h_to))
341 goto needs;
342 if (w & GCC && name_highbit(hp->h_cc))
343 goto needs;
344 if (w & GBCC && name_highbit(hp->h_bcc))
345 goto needs;
346 if (w & GSUBJECT && has_highbit(hp->h_subject))
347 goto needs;
348 return NULL;
349 needs: return getcharset(MIME_HIGHBIT);
352 #ifdef HAVE_ICONV
354 * Convert a string, upper-casing the characters.
356 static void
357 uppercopy(char *dest, const char *src)
360 *dest++ = upperconv(*src & 0377);
361 while (*src++);
365 * Strip dashes.
367 static void
368 stripdash(char *p)
370 char *q = p;
373 if (*(q = p) != '-')
374 q++;
375 while (*p++);
379 * An iconv_open wrapper that tries to convert between character set
380 * naming conventions.
382 iconv_t
383 iconv_open_ft(const char *tocode, const char *fromcode)
385 iconv_t id;
386 char *t, *f;
389 * On Linux systems, this call may succeed.
391 if ((id = iconv_open(tocode, fromcode)) != (iconv_t)-1)
392 return id;
394 * Remove the "iso-" prefixes for Solaris.
396 if (ascncasecmp(tocode, "iso-", 4) == 0)
397 tocode += 4;
398 else if (ascncasecmp(tocode, "iso", 3) == 0)
399 tocode += 3;
400 if (ascncasecmp(fromcode, "iso-", 4) == 0)
401 fromcode += 4;
402 else if (ascncasecmp(fromcode, "iso", 3) == 0)
403 fromcode += 3;
404 if (*tocode == '\0' || *fromcode == '\0')
405 return (iconv_t) -1;
406 if ((id = iconv_open(tocode, fromcode)) != (iconv_t)-1)
407 return id;
409 * Solaris prefers upper-case charset names. Don't ask...
411 t = salloc(strlen(tocode) + 1);
412 uppercopy(t, tocode);
413 f = salloc(strlen(fromcode) + 1);
414 uppercopy(f, fromcode);
415 if ((id = iconv_open(t, f)) != (iconv_t)-1)
416 return id;
418 * Strip dashes for UnixWare.
420 stripdash(t);
421 stripdash(f);
422 if ((id = iconv_open(t, f)) != (iconv_t)-1)
423 return id;
425 * Add your vendor's sillynesses here.
428 * If the encoding names are equal at this point, they
429 * are just not understood by iconv(), and we cannot
430 * sensibly use it in any way. We do not perform this
431 * as an optimization above since iconv() can otherwise
432 * be used to check the validity of the input even with
433 * identical encoding names.
435 if (strcmp(t, f) == 0)
436 errno = 0;
437 return (iconv_t)-1;
441 * Fault-tolerant iconv() function.
442 * (2012-09-24: export and use it exclusively to isolate prototype problems
443 * (*inb* is 'const char**' except in POSIX) in a single place.
444 * GNU libiconv even allows for configuration time const/non-const..
445 * In the end it's an ugly guess, but we can't do better since make(1) doesn't
446 * support compiler invocations which bail on error, so no -Werror.
448 /* Citrus project? */
449 #if defined _ICONV_H_ && defined __ICONV_F_HIDE_INVALID
450 # define __INBCAST (const char**)
451 #endif
452 #ifndef __INBCAST
453 # define __INBCAST
454 #endif
456 size_t
457 iconv_ft(iconv_t cd, char **inb, size_t *inbleft,
458 char **outb, size_t *outbleft, int tolerant)
460 size_t sz;
462 while ((sz = iconv(cd, __INBCAST inb, inbleft, outb, outbleft)) ==
463 (size_t)-1
464 #undef __INBCAST
465 && tolerant && (errno == EILSEQ || errno == EINVAL)) {
466 if (*inbleft > 0) {
467 (*inb)++;
468 (*inbleft)--;
469 } else {
470 **outb = '\0';
471 break;
473 if (*outbleft > 0) {
474 *(*outb)++ = '?';
475 (*outbleft)--;
476 } else {
477 **outb = '\0';
478 break;
481 return sz;
485 * Print an error because of an invalid character sequence.
487 /*ARGSUSED*/
488 static void
489 invalid_seq(int c)
491 (void)c;
492 /*fprintf(stderr, "iconv: cannot convert %c\n", c);*/
494 #endif /* HAVE_ICONV */
496 static int
497 is_this_enc(const char *line, const char *encoding)
499 int quoted = 0, c;
501 if (*line == '"')
502 quoted = 1, line++;
503 while (*line && *encoding)
504 if (c = *line++, lowerconv(c) != *encoding++)
505 return 0;
506 if (quoted && *line == '"')
507 return 1;
508 if (*line == '\0' || whitechar(*line & 0377))
509 return 1;
510 return 0;
514 * Get the mime encoding from a Content-Transfer-Encoding header field.
516 enum mimeenc
517 mime_getenc(char *p)
519 if (is_this_enc(p, "7bit"))
520 return MIME_7B;
521 if (is_this_enc(p, "8bit"))
522 return MIME_8B;
523 if (is_this_enc(p, "base64"))
524 return MIME_B64;
525 if (is_this_enc(p, "binary"))
526 return MIME_BIN;
527 if (is_this_enc(p, "quoted-printable"))
528 return MIME_QP;
529 return MIME_NONE;
533 * Get the mime content from a Content-Type header field, other parameters
534 * already stripped.
536 int
537 mime_getcontent(char *s)
539 if (strchr(s, '/') == NULL) /* for compatibility with non-MIME */
540 return MIME_TEXT;
541 if (asccasecmp(s, "text/plain") == 0)
542 return MIME_TEXT_PLAIN;
543 if (asccasecmp(s, "text/html") == 0)
544 return MIME_TEXT_HTML;
545 if (ascncasecmp(s, "text/", 5) == 0)
546 return MIME_TEXT;
547 if (asccasecmp(s, "message/rfc822") == 0)
548 return MIME_822;
549 if (ascncasecmp(s, "message/", 8) == 0)
550 return MIME_MESSAGE;
551 if (asccasecmp(s, "multipart/alternative") == 0)
552 return MIME_ALTERNATIVE;
553 if (asccasecmp(s, "multipart/digest") == 0)
554 return MIME_DIGEST;
555 if (ascncasecmp(s, "multipart/", 10) == 0)
556 return MIME_MULTI;
557 if (asccasecmp(s, "application/x-pkcs7-mime") == 0 ||
558 asccasecmp(s, "application/pkcs7-mime") == 0)
559 return MIME_PKCS7;
560 return MIME_UNKNOWN;
564 * Get a mime style parameter from a header line.
566 char *
567 mime_getparam(char *param, char *h)
569 char *p = h, *q, *r;
570 int c;
571 size_t sz;
573 sz = strlen(param);
574 if (!whitechar(*p & 0377)) {
575 c = '\0';
576 while (*p && (*p != ';' || c == '\\')) {
577 c = c == '\\' ? '\0' : *p;
578 p++;
580 if (*p++ == '\0')
581 return NULL;
583 for (;;) {
584 while (whitechar(*p & 0377))
585 p++;
586 if (ascncasecmp(p, param, sz) == 0) {
587 p += sz;
588 while (whitechar(*p & 0377))
589 p++;
590 if (*p++ == '=')
591 break;
593 c = '\0';
594 while (*p && (*p != ';' || c == '\\')) {
595 if (*p == '"' && c != '\\') {
596 p++;
597 while (*p && (*p != '"' || c == '\\')) {
598 c = c == '\\' ? '\0' : *p;
599 p++;
601 p++;
602 } else {
603 c = c == '\\' ? '\0' : *p;
604 p++;
607 if (*p++ == '\0')
608 return NULL;
610 while (whitechar(*p & 0377))
611 p++;
612 q = p;
613 c = '\0';
614 if (*p == '"') {
615 p++;
616 if ((q = strchr(p, '"')) == NULL)
617 return NULL;
618 } else {
619 q = p;
620 while (*q && !whitechar(*q & 0377) && *q != ';')
621 q++;
623 sz = q - p;
624 r = salloc(q - p + 1);
625 memcpy(r, p, sz);
626 *(r + sz) = '\0';
627 return r;
631 * Get the boundary out of a Content-Type: multipart/xyz header field.
633 char *
634 mime_getboundary(char *h)
636 char *p, *q;
637 size_t sz;
639 if ((p = mime_getparam("boundary", h)) == NULL)
640 return NULL;
641 sz = strlen(p);
642 q = salloc(sz + 3);
643 memcpy(q, "--", 2);
644 memcpy(q + 2, p, sz);
645 *(q + sz + 2) = '\0';
646 return q;
650 * Get a line like "text/html html" and look if x matches the extension.
652 static char *
653 mime_tline(char *x, char *l)
655 char *type, *n;
656 int match = 0;
658 if ((*l & 0200) || alphachar(*l & 0377) == 0)
659 return NULL;
660 type = l;
661 while (blankchar(*l & 0377) == 0 && *l != '\0')
662 l++;
663 if (*l == '\0')
664 return NULL;
665 *l++ = '\0';
666 while (blankchar(*l & 0377) != 0 && *l != '\0')
667 l++;
668 if (*l == '\0')
669 return NULL;
670 while (*l != '\0') {
671 n = l;
672 while (whitechar(*l & 0377) == 0 && *l != '\0')
673 l++;
674 if (*l != '\0')
675 *l++ = '\0';
676 if (strcmp(x, n) == 0) {
677 match = 1;
678 break;
680 while (whitechar(*l & 0377) != 0 && *l != '\0')
681 l++;
683 if (match != 0) {
684 n = salloc(strlen(type) + 1);
685 strcpy(n, type);
686 return n;
688 return NULL;
692 * Check the given MIME type file for extension ext.
694 static char *
695 mime_type(char *ext, char *filename)
697 FILE *f;
698 char *line = NULL;
699 size_t linesize = 0;
700 char *type = NULL;
702 if ((f = Fopen(filename, "r")) == NULL)
703 return NULL;
704 while (fgetline(&line, &linesize, NULL, NULL, f, 0)) {
705 if ((type = mime_tline(ext, line)) != NULL)
706 break;
708 Fclose(f);
709 if (line)
710 free(line);
711 return type;
715 * Return the Content-Type matching the extension of name.
717 char *
718 mime_filecontent(char *name)
720 char *ext, *content;
722 if ((ext = strrchr(name, '.')) == NULL || *++ext == '\0')
723 return NULL;
724 if ((content = mime_type(ext, expand(mimetypes_user))) != NULL)
725 return content;
726 if ((content = mime_type(ext, mimetypes_world)) != NULL)
727 return content;
728 return NULL;
732 * Check file contents.
734 static enum mimeclean
735 mime_isclean(FILE *f)
737 long initial_pos;
738 unsigned curlen = 1, maxlen = 0, limit = 950;
739 enum mimeclean isclean = 0;
740 char *cp;
741 int c = EOF, lastc;
743 initial_pos = ftell(f);
744 do {
745 lastc = c;
746 c = getc(f);
747 curlen++;
748 if (c == '\n' || c == EOF) {
750 * RFC 821 imposes a maximum line length of 1000
751 * characters including the terminating CRLF
752 * sequence. The configurable limit must not
753 * exceed that including a safety zone.
755 if (curlen > maxlen)
756 maxlen = curlen;
757 curlen = 1;
758 } else if (c & 0200) {
759 isclean |= MIME_HIGHBIT;
760 } else if (c == '\0') {
761 isclean |= MIME_HASNUL;
762 break;
763 } else if ((c < 040 && (c != '\t' && c != '\f')) || c == 0177) {
764 isclean |= MIME_CTRLCHAR;
766 } while (c != EOF);
767 if (lastc != '\n')
768 isclean |= MIME_NOTERMNL;
769 clearerr(f);
770 fseek(f, initial_pos, SEEK_SET);
771 if ((cp = value("maximum-unencoded-line-length")) != NULL)
772 limit = (unsigned)atoi(cp);
773 if (limit > 950)
774 limit = 950;
775 if (maxlen > limit)
776 isclean |= MIME_LONGLINES;
777 return isclean;
781 * Get the conversion that matches the encoding specified in the environment.
783 static enum conversion
784 gettextconversion(void)
786 char *p;
787 int convert;
789 if ((p = value("encoding")) == NULL)
790 return CONV_8BIT;
791 if (strcmp(p, "quoted-printable") == 0)
792 convert = CONV_TOQP;
793 else if (strcmp(p, "8bit") == 0)
794 convert = CONV_8BIT;
795 else {
796 fprintf(stderr, tr(177,
797 "Warning: invalid encoding %s, using 8bit\n"), p);
798 convert = CONV_8BIT;
800 return convert;
804 get_mime_convert(FILE *fp, char **contenttype, char **charset,
805 enum mimeclean *isclean, int dosign)
807 int convert;
809 *isclean = mime_isclean(fp);
810 if (*isclean & MIME_HASNUL ||
811 (*contenttype &&
812 ascncasecmp(*contenttype, "text/", 5))) {
813 convert = CONV_TOB64;
814 if (*contenttype == NULL ||
815 ascncasecmp(*contenttype, "text/", 5) == 0)
816 *contenttype = "application/octet-stream";
817 *charset = NULL;
818 } else if (*isclean & (MIME_LONGLINES|MIME_CTRLCHAR|MIME_NOTERMNL) ||
819 dosign)
820 convert = CONV_TOQP;
821 else if (*isclean & MIME_HIGHBIT)
822 convert = gettextconversion();
823 else
824 convert = CONV_7BIT;
825 if (*contenttype == NULL ||
826 ascncasecmp(*contenttype, "text/", 5) == 0) {
827 *charset = getcharset(*isclean);
828 if (wantcharset == (char *)-1) {
829 *contenttype = "application/octet-stream";
830 *charset = NULL;
831 } if (*isclean & MIME_CTRLCHAR) {
832 convert = CONV_TOB64;
834 * RFC 2046 forbids control characters other than
835 * ^I or ^L in text/plain bodies. However, some
836 * obscure character sets actually contain these
837 * characters, so the content type can be set.
839 if ((*contenttype = value("contenttype-cntrl")) == NULL)
840 *contenttype = "application/octet-stream";
841 } else if (*contenttype == NULL)
842 *contenttype = "text/plain";
844 return convert;
848 * Convert c to a hexadecimal character string and store it in hex.
850 static char *
851 ctohex(int c, char *hex)
853 unsigned char d;
855 hex[2] = '\0';
856 d = c % 16;
857 hex[1] = basetable[d];
858 if (c > d)
859 hex[0] = basetable[(c - d) / 16];
860 else
861 hex[0] = basetable[0];
862 return hex;
866 * Write to a file converting to quoted-printable.
867 * The mustquote function determines whether a character must be quoted.
869 static size_t
870 mime_write_toqp(struct str *in, FILE *fo, int (*mustquote)(int))
872 char *p, *upper, *h, hex[3];
873 int l;
874 size_t sz;
876 sz = in->l;
877 upper = in->s + in->l;
878 for (p = in->s, l = 0; p < upper; p++) {
879 if (mustquote(*p&0377) ||
880 (p < upper-1 && p[1] == '\n' &&
881 blankchar(p[0]&0377)) ||
882 (p < upper-4 && l == 0 &&
883 p[0] == 'F' && p[1] == 'r' &&
884 p[2] == 'o' && p[3] == 'm') ||
885 (*p == '.' && l == 0 && p < upper-1 &&
886 p[1] == '\n')) {
887 if (l >= 69) {
888 sz += 2;
889 fwrite("=\n", sizeof (char), 2, fo);
890 l = 0;
892 sz += 2;
893 putc('=', fo);
894 h = ctohex(*p&0377, hex);
895 fwrite(h, sizeof *h, 2, fo);
896 l += 3;
897 } else {
898 if (*p == '\n')
899 l = 0;
900 else if (l >= 71) {
901 sz += 2;
902 fwrite("=\n", sizeof (char), 2, fo);
903 l = 0;
905 putc(*p, fo);
906 l++;
909 return sz;
913 * Write to a stringstruct converting to quoted-printable.
914 * The mustquote function determines whether a character must be quoted.
916 static void
917 mime_str_toqp(struct str *in, struct str *out, int (*mustquote)(int), int inhdr)
919 char *p, *q, *upper;
921 out->s = smalloc(in->l * 3 + 1);
922 q = out->s;
923 out->l = in->l;
924 upper = in->s + in->l;
925 for (p = in->s; p < upper; p++) {
926 if (mustquote(*p&0377) || (p+1 < upper && *(p + 1) == '\n' &&
927 blankchar(*p & 0377))) {
928 if (inhdr && *p == ' ') {
929 *q++ = '_';
930 } else {
931 out->l += 2;
932 *q++ = '=';
933 ctohex(*p&0377, q);
934 q += 2;
936 } else {
937 *q++ = *p;
940 *q = '\0';
944 * Write to a stringstruct converting from quoted-printable.
946 static void
947 mime_fromqp(struct str *in, struct str *out, int ishdr)
949 char *p, *q, *upper;
950 char quote[4];
952 out->l = in->l;
953 out->s = smalloc(out->l + 1);
954 upper = in->s + in->l;
955 for (p = in->s, q = out->s; p < upper; p++) {
956 if (*p == '=') {
957 do {
958 p++;
959 out->l--;
960 } while (blankchar(*p & 0377) && p < upper);
961 if (p == upper)
962 break;
963 if (*p == '\n') {
964 out->l--;
965 continue;
967 if (p + 1 >= upper)
968 break;
969 quote[0] = *p++;
970 quote[1] = *p;
971 quote[2] = '\0';
972 *q = (char)strtol(quote, NULL, 16);
973 q++;
974 out->l--;
975 } else if (ishdr && *p == '_')
976 *q++ = ' ';
977 else
978 *q++ = *p;
980 return;
983 #define mime_fromhdr_inc(inc) { \
984 size_t diff = q - out->s; \
985 out->s = srealloc(out->s, (maxstor += inc) + 1); \
986 q = &(out->s)[diff]; \
989 * Convert header fields from RFC 1522 format
991 void
992 mime_fromhdr(struct str *in, struct str *out, enum tdflags flags)
994 char *p, *q, *op, *upper, *cs, *cbeg, *tcs, *lastwordend = NULL;
995 struct str cin, cout;
996 int convert;
997 size_t maxstor, lastoutl = 0;
998 #ifdef HAVE_ICONV
999 iconv_t fhicd = (iconv_t)-1;
1000 #endif
1002 tcs = gettcharset();
1003 maxstor = in->l;
1004 out->s = smalloc(maxstor + 1);
1005 out->l = 0;
1006 upper = in->s + in->l;
1007 for (p = in->s, q = out->s; p < upper; p++) {
1008 op = p;
1009 if (*p == '=' && *(p + 1) == '?') {
1010 p += 2;
1011 cbeg = p;
1012 while (p < upper && *p != '?')
1013 p++; /* strip charset */
1014 if (p >= upper)
1015 goto notmime;
1016 cs = salloc(++p - cbeg);
1017 memcpy(cs, cbeg, p - cbeg - 1);
1018 cs[p - cbeg - 1] = '\0';
1019 #ifdef HAVE_ICONV
1020 if (fhicd != (iconv_t)-1)
1021 iconv_close(fhicd);
1022 if (strcmp(cs, tcs))
1023 fhicd = iconv_open_ft(tcs, cs);
1024 else
1025 fhicd = (iconv_t)-1;
1026 #endif
1027 switch (*p) {
1028 case 'B': case 'b':
1029 convert = CONV_FROMB64;
1030 break;
1031 case 'Q': case 'q':
1032 convert = CONV_FROMQP;
1033 break;
1034 default: /* invalid, ignore */
1035 goto notmime;
1037 if (*++p != '?')
1038 goto notmime;
1039 cin.s = ++p;
1040 cin.l = 1;
1041 for (;;) {
1042 if (p == upper)
1043 goto fromhdr_end;
1044 if (*p++ == '?' && *p == '=')
1045 break;
1046 cin.l++;
1048 cin.l--;
1049 switch (convert) {
1050 case CONV_FROMB64:
1051 mime_fromb64(&cin, &cout, 1);
1052 break;
1053 case CONV_FROMQP:
1054 mime_fromqp(&cin, &cout, 1);
1055 break;
1057 if (lastwordend) {
1058 q = lastwordend;
1059 out->l = lastoutl;
1061 #ifdef HAVE_ICONV
1062 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
1063 char *iptr, *mptr, *nptr, *uptr;
1064 size_t inleft, outleft;
1066 again: inleft = cout.l;
1067 outleft = maxstor - out->l;
1068 mptr = nptr = q;
1069 uptr = nptr + outleft;
1070 iptr = cout.s;
1071 if (iconv_ft(fhicd, &iptr, &inleft,
1072 &nptr, &outleft, 0) == (size_t)-1 &&
1073 errno == E2BIG) {
1074 iconv_ft(fhicd, NULL, NULL, NULL, NULL,
1076 mime_fromhdr_inc(inleft);
1077 goto again;
1080 * For state-dependent encodings,
1081 * reset the state here, assuming
1082 * that states are restricted to
1083 * single encoded-word parts.
1085 while (iconv_ft(fhicd, NULL, NULL,
1086 &nptr, &outleft, 0) == (size_t)-1 &&
1087 errno == E2BIG)
1088 mime_fromhdr_inc(16);
1089 out->l += uptr - mptr - outleft;
1090 q += uptr - mptr - outleft;
1091 } else {
1092 #endif
1093 while (cout.l > maxstor - out->l)
1094 mime_fromhdr_inc(cout.l -
1095 (maxstor - out->l));
1096 memcpy(q, cout.s, cout.l);
1097 q += cout.l;
1098 out->l += cout.l;
1099 #ifdef HAVE_ICONV
1101 #endif
1102 free(cout.s);
1103 lastwordend = q;
1104 lastoutl = out->l;
1105 } else {
1106 notmime:
1107 p = op;
1108 while (out->l >= maxstor)
1109 mime_fromhdr_inc(16);
1110 *q++ = *p;
1111 out->l++;
1112 if (!blankchar(*p&0377))
1113 lastwordend = NULL;
1116 fromhdr_end:
1117 *q = '\0';
1118 if (flags & TD_ISPR) {
1119 struct str new;
1120 makeprint(out, &new);
1121 free(out->s);
1122 *out = new;
1124 if (flags & TD_DELCTRL)
1125 out->l = delctrl(out->s, out->l);
1126 #ifdef HAVE_ICONV
1127 if (fhicd != (iconv_t)-1)
1128 iconv_close(fhicd);
1129 #endif
1130 return;
1134 * Convert header fields to RFC 1522 format and write to the file fo.
1136 static size_t
1137 mime_write_tohdr(struct str *in, FILE *fo)
1139 char *upper, *wbeg, *wend, *charset, *lastwordend = NULL, *lastspc, b,
1140 *charset7;
1141 struct str cin, cout;
1142 size_t sz = 0, col = 0, wr, charsetlen, charset7len;
1143 int quoteany, mustquote, broken,
1144 maxcol = 65 /* there is the header field's name, too */;
1146 upper = in->s + in->l;
1147 charset = getcharset(MIME_HIGHBIT);
1148 if ((charset7 = value("charset7")) == NULL)
1149 charset7 = us_ascii;
1150 charsetlen = strlen(charset);
1151 charset7len = strlen(charset7);
1152 charsetlen = smax(charsetlen, charset7len);
1153 b = 0;
1154 for (wbeg = in->s, quoteany = 0; wbeg < upper; wbeg++) {
1155 b |= *wbeg;
1156 if (mustquote_hdr(wbeg, wbeg == in->s, wbeg == &upper[-1]))
1157 quoteany++;
1159 if (2u * quoteany > in->l) {
1161 * Print the entire field in base64.
1163 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
1164 wend = upper;
1165 cin.s = wbeg;
1166 for (;;) {
1167 cin.l = wend - wbeg;
1168 if (cin.l * 4/3 + 7 + charsetlen
1169 < maxcol - col) {
1170 fprintf(fo, "=?%s?B?",
1171 b&0200 ? charset : charset7);
1172 wr = mime_write_tob64(&cin, fo, 1);
1173 fwrite("?=", sizeof (char), 2, fo);
1174 wr += 7 + charsetlen;
1175 sz += wr, col += wr;
1176 if (wend < upper) {
1177 fwrite("\n ", sizeof (char),
1178 2, fo);
1179 sz += 2;
1180 col = 0;
1181 maxcol = 76;
1183 break;
1184 } else {
1185 if (col) {
1186 fprintf(fo, "\n ");
1187 sz += 2;
1188 col = 0;
1189 maxcol = 76;
1190 } else
1191 wend -= 4;
1195 } else {
1197 * Print the field word-wise in quoted-printable.
1199 broken = 0;
1200 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
1201 lastspc = NULL;
1202 while (wbeg < upper && whitechar(*wbeg & 0377)) {
1203 lastspc = lastspc ? lastspc : wbeg;
1204 wbeg++;
1205 col++;
1206 broken = 0;
1208 if (wbeg == upper) {
1209 if (lastspc)
1210 while (lastspc < wbeg) {
1211 putc(*lastspc&0377, fo);
1212 lastspc++,
1213 sz++;
1215 break;
1217 mustquote = 0;
1218 b = 0;
1219 for (wend = wbeg;
1220 wend < upper && !whitechar(*wend & 0377);
1221 wend++) {
1222 b |= *wend;
1223 if (mustquote_hdr(wend, wend == wbeg,
1224 wbeg == &upper[-1]))
1225 mustquote++;
1227 if (mustquote || broken ||
1228 ((wend - wbeg) >= 74 && quoteany)) {
1229 for (;;) {
1230 cin.s = lastwordend ? lastwordend :
1231 wbeg;
1232 cin.l = wend - cin.s;
1233 mime_str_toqp(&cin, &cout,
1234 mustquote_inhdrq, 1);
1235 if ((wr = cout.l + charsetlen + 7)
1236 < maxcol - col) {
1237 if (lastspc)
1238 while (lastspc < wbeg) {
1239 putc(*lastspc
1240 &0377,
1241 fo);
1242 lastspc++,
1243 sz++;
1245 fprintf(fo, "=?%s?Q?", b&0200 ?
1246 charset : charset7);
1247 fwrite(cout.s, sizeof *cout.s,
1248 cout.l, fo);
1249 fwrite("?=", 1, 2, fo);
1250 sz += wr, col += wr;
1251 free(cout.s);
1252 break;
1253 } else {
1254 broken = 1;
1255 if (col) {
1256 putc('\n', fo);
1257 sz++;
1258 col = 0;
1259 maxcol = 76;
1260 if (lastspc == NULL) {
1261 putc(' ', fo);
1262 sz++;
1263 maxcol--;
1264 } else
1265 maxcol -= wbeg -
1266 lastspc;
1267 } else {
1268 wend -= 4;
1270 free(cout.s);
1273 lastwordend = wend;
1274 } else {
1275 if (col &&
1276 (size_t)(wend - wbeg) > maxcol - col) {
1277 putc('\n', fo);
1278 sz++;
1279 col = 0;
1280 maxcol = 76;
1281 if (lastspc == NULL) {
1282 putc(' ', fo);
1283 sz++;
1284 maxcol--;
1285 } else
1286 maxcol -= wbeg - lastspc;
1288 if (lastspc)
1289 while (lastspc < wbeg) {
1290 putc(*lastspc&0377, fo);
1291 lastspc++, sz++;
1293 wr = fwrite(wbeg, sizeof *wbeg,
1294 wend - wbeg, fo);
1295 sz += wr, col += wr;
1296 lastwordend = NULL;
1300 return sz;
1304 * Write len characters of the passed string to the passed file,
1305 * doing charset and header conversion.
1307 static size_t
1308 convhdra(char *str, size_t len, FILE *fp)
1310 #ifdef HAVE_ICONV
1311 char *ip, *op;
1312 size_t isz, osz;
1313 #endif
1314 struct str cin;
1315 size_t cbufsz;
1316 char *cbuf;
1317 size_t sz;
1319 cbuf = ac_alloc(cbufsz = 1);
1320 #ifdef HAVE_ICONV
1321 if (iconvd == (iconv_t)-1) {
1322 #endif
1323 cin.s = str;
1324 cin.l = len;
1325 #ifdef HAVE_ICONV
1326 } else {
1327 again: ip = str;
1328 isz = len;
1329 op = cbuf;
1330 osz = cbufsz;
1331 if (iconv_ft(iconvd, &ip, &isz, &op, &osz, 0) == (size_t)-1) {
1332 if (errno != E2BIG) {
1333 ac_free(cbuf);
1334 return 0;
1336 cbuf = ac_alloc(cbufsz += isz);
1337 goto again;
1339 cin.s = cbuf;
1340 cin.l = cbufsz - osz;
1342 #endif /* HAVE_ICONV */
1343 sz = mime_write_tohdr(&cin, fp);
1344 ac_free(cbuf);
1345 return sz;
1350 * Write an address to a header field.
1352 static size_t
1353 mime_write_tohdr_a(struct str *in, FILE *f)
1355 char *cp, *lastcp;
1356 size_t sz = 0;
1358 in->s[in->l] = '\0';
1359 lastcp = in->s;
1360 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
1361 sz += convhdra(lastcp, cp - lastcp, f);
1362 lastcp = cp;
1363 } else
1364 cp = in->s;
1365 for ( ; *cp; cp++) {
1366 switch (*cp) {
1367 case '(':
1368 sz += fwrite(lastcp, 1, cp - lastcp + 1, f);
1369 lastcp = ++cp;
1370 cp = skip_comment(cp);
1371 if (--cp > lastcp)
1372 sz += convhdra(lastcp, cp - lastcp, f);
1373 lastcp = cp;
1374 break;
1375 case '"':
1376 while (*cp) {
1377 if (*++cp == '"')
1378 break;
1379 if (*cp == '\\' && cp[1])
1380 cp++;
1382 break;
1385 if (cp > lastcp)
1386 sz += fwrite(lastcp, 1, cp - lastcp, f);
1387 return sz;
1390 static void
1391 addstr(char **buf, size_t *sz, size_t *pos, char *str, size_t len)
1393 *buf = srealloc(*buf, *sz += len);
1394 memcpy(&(*buf)[*pos], str, len);
1395 *pos += len;
1398 static void
1399 addconv(char **buf, size_t *sz, size_t *pos, char *str, size_t len)
1401 struct str in, out;
1403 in.s = str;
1404 in.l = len;
1405 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
1406 addstr(buf, sz, pos, out.s, out.l);
1407 free(out.s);
1411 * Interpret MIME strings in parts of an address field.
1413 char *
1414 mime_fromaddr(char *name)
1416 char *cp, *lastcp;
1417 char *res = NULL;
1418 size_t ressz = 1, rescur = 0;
1420 if (name == NULL || *name == '\0')
1421 return name;
1422 if ((cp = routeaddr(name)) != NULL && cp > name) {
1423 addconv(&res, &ressz, &rescur, name, cp - name);
1424 lastcp = cp;
1425 } else
1426 cp = lastcp = name;
1427 for ( ; *cp; cp++) {
1428 switch (*cp) {
1429 case '(':
1430 addstr(&res, &ressz, &rescur, lastcp, cp - lastcp + 1);
1431 lastcp = ++cp;
1432 cp = skip_comment(cp);
1433 if (--cp > lastcp)
1434 addconv(&res, &ressz, &rescur, lastcp,
1435 cp - lastcp);
1436 lastcp = cp;
1437 break;
1438 case '"':
1439 while (*cp) {
1440 if (*++cp == '"')
1441 break;
1442 if (*cp == '\\' && cp[1])
1443 cp++;
1445 break;
1448 if (cp > lastcp)
1449 addstr(&res, &ressz, &rescur, lastcp, cp - lastcp);
1450 res[rescur] = '\0';
1451 cp = savestr(res);
1452 free(res);
1453 return cp;
1457 * fwrite whilst adding prefix, if present.
1459 size_t
1460 prefixwrite(void *ptr, size_t size, size_t nmemb, FILE *f,
1461 char *prefix, size_t prefixlen)
1463 static FILE *lastf;
1464 static char lastc = '\n';
1465 size_t lpref, i, qfold = 0, lnlen = 0, rsz = size * nmemb, wsz = 0;
1466 char *p, *maxp, c;
1468 if (rsz == 0)
1469 return 0;
1470 if (prefix == NULL) {
1471 lastf = f;
1472 lastc = ((char *)ptr)[rsz - 1];
1473 return fwrite(ptr, 1, rsz, f);
1476 if ((p = value("quote-fold")) != NULL) {
1477 qfold = (size_t)strtol(p, NULL, 10);
1478 if (qfold < prefixlen + 4)
1479 qfold = prefixlen + 4;
1480 --qfold; /* The newline escape */
1483 if (f != lastf || lastc == '\n') {
1484 wsz += fwrite(prefix, sizeof *prefix, prefixlen, f);
1485 lnlen = prefixlen;
1487 lastf = f;
1489 p = ptr;
1490 maxp = p + rsz;
1492 if (! qfold) {
1493 for (;;) {
1494 c = *p++;
1495 putc(c, f);
1496 wsz++;
1497 if (p == maxp)
1498 break;
1499 if (c != '\n')
1500 continue;
1501 wsz += fwrite(prefix, sizeof *prefix, prefixlen, f);
1503 } else {
1504 for (;;) {
1506 * After writing a real newline followed by our prefix,
1507 * compress the quoted prefixes
1509 for (lpref = 0; p != maxp;) {
1510 /* (c: keep cc happy) */
1511 for (c = i = 0; p + i < maxp;) {
1512 c = p[i++];
1513 if (blankspacechar(c)) /* XXX U+A0+ */
1514 continue;
1515 if (! ISQUOTE(c))
1516 goto jquoteok;
1517 break;
1519 p += i;
1520 ++lpref;
1521 putc(c, f);
1522 ++wsz;
1524 jquoteok: lnlen += lpref;
1526 jsoftnl: /*
1527 * Search forward until either *quote-fold* or NL.
1528 * In the former case try to break at whitespace,
1529 * but only if that lies in the 2nd half of the data
1531 for (c = rsz = i = 0; p + i < maxp;) {
1532 c = p[i++];
1533 if (c == '\n')
1534 break;
1535 if (spacechar(c))
1536 rsz = i;
1537 if (lnlen + i >= qfold) {
1538 c = 0;
1539 if (rsz > qfold >> 1)
1540 i = rsz;
1541 break;
1545 if (i > 0) {
1546 wsz += fwrite(p, sizeof *p, i, f);
1547 p += i;
1549 if (p >= maxp)
1550 break;
1552 if (c != '\n') {
1553 putc('\\', f);
1554 putc('\n', f);
1555 wsz += 2;
1558 wsz += fwrite(prefix, sizeof *prefix, prefixlen, f);
1559 lnlen = prefixlen;
1560 if (c == '\n')
1561 continue;
1563 if ((i = lpref)) {
1564 for (; i > 0; ++wsz, ++lnlen, --i)
1565 (void)putc('.', f);
1566 (void)putc(' ', f);
1567 ++wsz;
1568 ++lnlen;
1570 goto jsoftnl;
1574 lastc = p[-1];
1575 return wsz;
1579 * fwrite while checking for displayability.
1581 static size_t
1582 fwrite_td(void *ptr, size_t size, size_t nmemb, FILE *f, enum tdflags flags,
1583 char *prefix, size_t prefixlen)
1585 char *upper;
1586 size_t sz, csize;
1587 #ifdef HAVE_ICONV
1588 char *iptr, *nptr;
1589 size_t inleft, outleft;
1590 #endif
1591 char *mptr, *xmptr, *mlptr = NULL;
1592 size_t mptrsz;
1594 csize = size * nmemb;
1595 mptrsz = csize;
1596 mptr = xmptr = ac_alloc(mptrsz + 1);
1597 #ifdef HAVE_ICONV
1598 if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
1599 again: inleft = csize;
1600 outleft = mptrsz;
1601 nptr = mptr;
1602 iptr = ptr;
1603 if (iconv_ft(iconvd, &iptr, &inleft, &nptr, &outleft, 0) ==
1604 (size_t)-1 &&
1605 errno == E2BIG) {
1606 iconv_ft(iconvd, NULL, NULL, NULL, NULL, 0);
1607 ac_free(mptr);
1608 mptrsz += inleft;
1609 mptr = ac_alloc(mptrsz + 1);
1610 goto again;
1612 nmemb = mptrsz - outleft;
1613 size = sizeof (char);
1614 ptr = mptr;
1615 csize = size * nmemb;
1616 } else
1617 #endif
1619 memcpy(mptr, ptr, csize);
1621 upper = mptr + csize;
1622 *upper = '\0';
1623 if (flags & TD_ISPR) {
1624 struct str in, out;
1625 in.s = mptr;
1626 in.l = csize;
1627 makeprint(&in, &out);
1628 mptr = mlptr = out.s;
1629 csize = out.l;
1631 if (flags & TD_DELCTRL)
1632 csize = delctrl(mptr, csize);
1633 sz = prefixwrite(mptr, sizeof *mptr, csize, f, prefix, prefixlen);
1634 ac_free(xmptr);
1635 free(mlptr);
1636 return sz;
1640 * fwrite performing the given MIME conversion.
1642 size_t
1643 mime_write(void *ptr, size_t size, FILE *f,
1644 enum conversion convert, enum tdflags dflags,
1645 char *prefix, size_t prefixlen,
1646 char **restp, size_t *restsizep)
1648 struct str in, out;
1649 size_t sz, csize;
1650 int is_text = 0;
1651 #ifdef HAVE_ICONV
1652 char mptr[LINESIZE * 6];
1653 char *iptr, *nptr;
1654 size_t inleft, outleft;
1655 #endif
1657 if (size == 0)
1658 return 0;
1659 csize = size;
1660 #ifdef HAVE_ICONV
1661 if (csize < sizeof mptr && (dflags & TD_ICONV)
1662 && iconvd != (iconv_t)-1
1663 && (convert == CONV_TOQP || convert == CONV_8BIT ||
1664 convert == CONV_TOB64 ||
1665 convert == CONV_TOHDR)) {
1666 inleft = csize;
1667 outleft = sizeof mptr;
1668 nptr = mptr;
1669 iptr = ptr;
1670 if (iconv_ft(iconvd, &iptr, &inleft,
1671 &nptr, &outleft, 0) != (size_t)-1) {
1672 in.l = sizeof mptr - outleft;
1673 in.s = mptr;
1674 } else {
1675 if (errno == EILSEQ || errno == EINVAL)
1676 invalid_seq(*iptr);
1677 return 0;
1679 } else {
1680 #endif
1681 in.s = ptr;
1682 in.l = csize;
1683 #ifdef HAVE_ICONV
1685 #endif
1686 switch (convert) {
1687 case CONV_FROMQP:
1688 mime_fromqp(&in, &out, 0);
1689 sz = fwrite_td(out.s, sizeof *out.s, out.l, f, dflags,
1690 prefix, prefixlen);
1691 free(out.s);
1692 break;
1693 case CONV_TOQP:
1694 sz = mime_write_toqp(&in, f, mustquote_body);
1695 break;
1696 case CONV_8BIT:
1697 sz = prefixwrite(in.s, sizeof *in.s, in.l, f,
1698 prefix, prefixlen);
1699 break;
1700 case CONV_FROMB64_T:
1701 is_text = 1;
1702 /*FALLTHROUGH*/
1703 case CONV_FROMB64:
1704 mime_fromb64_b(&in, &out, is_text, f);
1705 if (is_text && out.s[out.l-1] != '\n' && restp && restsizep) {
1706 *restp = ptr;
1707 *restsizep = size;
1708 sz = 0;
1709 } else {
1710 sz = fwrite_td(out.s, sizeof *out.s, out.l, f, dflags,
1711 prefix, prefixlen);
1713 free(out.s);
1714 break;
1715 case CONV_TOB64:
1716 sz = mime_write_tob64(&in, f, 0);
1717 break;
1718 case CONV_FROMHDR:
1719 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
1720 sz = fwrite_td(out.s, sizeof *out.s, out.l, f,
1721 dflags&TD_DELCTRL, prefix, prefixlen);
1722 free(out.s);
1723 break;
1724 case CONV_TOHDR:
1725 sz = mime_write_tohdr(&in, f);
1726 break;
1727 case CONV_TOHDR_A:
1728 sz = mime_write_tohdr_a(&in, f);
1729 break;
1730 default:
1731 sz = fwrite_td(in.s, sizeof *in.s, in.l, f, dflags,
1732 prefix, prefixlen);
1734 return sz;