mime.c: fix compiler warnings
[s-mailx.git] / mime.c
blob037dffb8826f3d29adb5d246a0f600ece32239f9
1 /*
2 * Heirloom mailx - 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 #ifndef lint
41 #ifdef DOSCCS
42 static char copyright[]
43 = "@(#) Copyright (c) 2000, 2002 Gunnar Ritter. All rights reserved.\n";
44 static char sccsid[] = "@(#)mime.c 2.71 (gritter) 7/5/10";
45 #endif /* DOSCCS */
46 #endif /* not lint */
48 #include "rcv.h"
49 #include "extern.h"
50 #include <ctype.h>
51 #include <errno.h>
52 #ifdef HAVE_WCTYPE_H
53 #include <wctype.h>
54 #endif /* HAVE_WCTYPE_H */
57 * Mail -- a mail program
59 * MIME support functions.
63 * You won't guess what these are for.
65 static const char basetable[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
66 static char *mimetypes_world = "/etc/mime.types";
67 static char *mimetypes_user = "~/.mime.types";
68 char *us_ascii = "us-ascii";
70 static int mustquote_body(int c);
71 static int mustquote_hdr(const char *cp, int wordstart, int wordend);
72 static int mustquote_inhdrq(int c);
73 static size_t delctrl(char *cp, size_t sz);
74 static char *getcharset(int isclean);
75 static int has_highbit(register const char *s);
76 #ifdef HAVE_ICONV
77 static void uppercopy(char *dest, const char *src);
78 static void stripdash(char *p);
79 static size_t iconv_ft(iconv_t cd, char **inb, size_t *inbleft,
80 char **outb, size_t *outbleft);
81 static void invalid_seq(int c);
82 #endif /* HAVE_ICONV */
83 static int is_this_enc(const char *line, const char *encoding);
84 static char *mime_tline(char *x, char *l);
85 static char *mime_type(char *ext, char *filename);
86 static enum mimeclean mime_isclean(FILE *f);
87 static enum conversion gettextconversion(void);
88 static char *ctohex(int c, char *hex);
89 static size_t mime_write_toqp(struct str *in, FILE *fo, int (*mustquote)(int));
90 static void mime_str_toqp(struct str *in, struct str *out,
91 int (*mustquote)(int), int inhdr);
92 static void mime_fromqp(struct str *in, struct str *out, int ishdr);
93 static size_t mime_write_tohdr(struct str *in, FILE *fo);
94 static size_t convhdra(char *str, size_t len, FILE *fp);
95 static size_t mime_write_tohdr_a(struct str *in, FILE *f);
96 static void addstr(char **buf, size_t *sz, size_t *pos, char *str, size_t len);
97 static void addconv(char **buf, size_t *sz, size_t *pos, char *str, size_t len);
98 static size_t fwrite_td(void *ptr, size_t size, size_t nmemb, FILE *f,
99 enum tdflags flags, char *prefix, size_t prefixlen);
102 * Check if c must be quoted inside a message's body.
104 static int
105 mustquote_body(int c)
107 if (c != '\n' && (c < 040 || c == '=' || c >= 0177))
108 return 1;
109 return 0;
113 * Check if c must be quoted inside a message's header.
115 static int
116 mustquote_hdr(const char *cp, int wordstart, int wordend)
118 int c = *cp & 0377;
120 if (c != '\n' && (c < 040 || c >= 0177))
121 return 1;
122 if (wordstart && cp[0] == '=' && cp[1] == '?')
123 return 1;
124 if (cp[0] == '?' && cp[1] == '=' &&
125 (wordend || cp[2] == '\0' || whitechar(cp[2]&0377)))
126 return 1;
127 return 0;
131 * Check if c must be quoted inside a quoting in a message's header.
133 static int
134 mustquote_inhdrq(int c)
136 if (c != '\n'
137 && (c <= 040 || c == '=' || c == '?' || c == '_' || c >= 0177))
138 return 1;
139 return 0;
142 static size_t
143 delctrl(char *cp, size_t sz)
145 size_t x = 0, y = 0;
147 while (x < sz) {
148 if (!cntrlchar(cp[x]&0377))
149 cp[y++] = cp[x];
150 x++;
152 return y;
156 * Check if a name's address part contains invalid characters.
158 int
159 mime_name_invalid(char *name, int putmsg)
161 char *addr, *p;
162 int in_quote = 0, in_domain = 0, err = 0, hadat = 0;
164 if (is_fileaddr(name))
165 return 0;
166 addr = skin(name);
168 if (addr == NULL || *addr == '\0')
169 return 1;
170 for (p = addr; *p != '\0'; p++) {
171 if (*p == '\"') {
172 in_quote = !in_quote;
173 } else if (*p < 040 || (*p & 0377) >= 0177) {
174 err = *p & 0377;
175 break;
176 } else if (in_domain == 2) {
177 if ((*p == ']' && p[1] != '\0') || *p == '\0'
178 || *p == '\\' || whitechar(*p & 0377)) {
179 err = *p & 0377;
180 break;
182 } else if (in_quote && in_domain == 0) {
183 /*EMPTY*/;
184 } else if (*p == '\\' && p[1] != '\0') {
185 p++;
186 } else if (*p == '@') {
187 if (hadat++) {
188 if (putmsg) {
189 fprintf(stderr, catgets(catd, CATSET,
190 142,
191 "%s contains invalid @@ sequence\n"),
192 addr);
193 putmsg = 0;
195 err = *p;
196 break;
198 if (p[1] == '[')
199 in_domain = 2;
200 else
201 in_domain = 1;
202 continue;
203 } else if (*p == '(' || *p == ')' || *p == '<' || *p == '>'
204 || *p == ',' || *p == ';' || *p == ':'
205 || *p == '\\' || *p == '[' || *p == ']') {
206 err = *p & 0377;
207 break;
209 hadat = 0;
211 if (err && putmsg) {
212 fprintf(stderr, catgets(catd, CATSET, 143,
213 "%s contains invalid character '"), addr);
214 #ifdef HAVE_SETLOCALE
215 if (isprint(err))
216 #else /* !HAVE_SETLOCALE */
217 if (err >= 040 && err <= 0177)
218 #endif /* !HAVE_SETLOCALE */
219 putc(err, stderr);
220 else
221 fprintf(stderr, "\\%03o", err);
222 fprintf(stderr, catgets(catd, CATSET, 144, "'\n"));
224 return err;
228 * Check all addresses in np and delete invalid ones.
230 struct name *
231 checkaddrs(struct name *np)
233 struct name *n = np;
235 while (n != NULL) {
236 if (mime_name_invalid(n->n_name, 1)) {
237 if (n->n_blink)
238 n->n_blink->n_flink = n->n_flink;
239 if (n->n_flink)
240 n->n_flink->n_blink = n->n_blink;
241 if (n == np)
242 np = n->n_flink;
244 n = n->n_flink;
246 return np;
249 static char defcharset[] = "utf-8";
252 * Get the character set dependant on the conversion.
254 static char *
255 getcharset(int isclean)
257 char *charset;
259 if (isclean & (MIME_CTRLCHAR|MIME_HASNUL))
260 charset = NULL;
261 else if (isclean & MIME_HIGHBIT) {
262 charset = (wantcharset && wantcharset != (char *)-1) ?
263 wantcharset : value("charset");
264 if (charset == NULL) {
265 charset = defcharset;
267 } else {
269 * This variable shall remain undocumented because
270 * only experts should change it.
272 charset = value("charset7");
273 if (charset == NULL) {
274 charset = us_ascii;
277 return charset;
281 * Get the setting of the terminal's character set.
283 char *
284 gettcharset(void)
286 char *t;
288 if ((t = value("ttycharset")) == NULL)
289 if ((t = value("charset")) == NULL)
290 t = defcharset;
291 return t;
294 static int
295 has_highbit(const char *s)
297 if (s) {
299 if (*s & 0200)
300 return 1;
301 while (*s++ != '\0');
303 return 0;
306 static int
307 name_highbit(struct name *np)
309 while (np) {
310 if (has_highbit(np->n_name) || has_highbit(np->n_fullname))
311 return 1;
312 np = np->n_flink;
314 return 0;
317 char *
318 need_hdrconv(struct header *hp, enum gfield w)
320 if (w & GIDENT) {
321 if (hp->h_from) {
322 if (name_highbit(hp->h_from))
323 goto needs;
324 } else if (has_highbit(myaddrs(hp)))
325 goto needs;
326 if (hp->h_organization) {
327 if (has_highbit(hp->h_organization))
328 goto needs;
329 } else if (has_highbit(value("ORGANIZATION")))
330 goto needs;
331 if (hp->h_replyto) {
332 if (name_highbit(hp->h_replyto))
333 goto needs;
334 } else if (has_highbit(value("replyto")))
335 goto needs;
336 if (hp->h_sender) {
337 if (name_highbit(hp->h_sender))
338 goto needs;
339 } else if (has_highbit(value("sender")))
340 goto needs;
342 if (w & GTO && name_highbit(hp->h_to))
343 goto needs;
344 if (w & GCC && name_highbit(hp->h_cc))
345 goto needs;
346 if (w & GBCC && name_highbit(hp->h_bcc))
347 goto needs;
348 if (w & GSUBJECT && has_highbit(hp->h_subject))
349 goto needs;
350 return NULL;
351 needs: return getcharset(MIME_HIGHBIT);
354 #ifdef HAVE_ICONV
356 * Convert a string, upper-casing the characters.
358 static void
359 uppercopy(char *dest, const char *src)
362 *dest++ = upperconv(*src & 0377);
363 while (*src++);
367 * Strip dashes.
369 static void
370 stripdash(char *p)
372 char *q = p;
375 if (*(q = p) != '-')
376 q++;
377 while (*p++);
381 * An iconv_open wrapper that tries to convert between character set
382 * naming conventions.
384 iconv_t
385 iconv_open_ft(const char *tocode, const char *fromcode)
387 iconv_t id;
388 char *t, *f;
391 * On Linux systems, this call may succeed.
393 if ((id = iconv_open(tocode, fromcode)) != (iconv_t)-1)
394 return id;
396 * Remove the "iso-" prefixes for Solaris.
398 if (ascncasecmp(tocode, "iso-", 4) == 0)
399 tocode += 4;
400 else if (ascncasecmp(tocode, "iso", 3) == 0)
401 tocode += 3;
402 if (ascncasecmp(fromcode, "iso-", 4) == 0)
403 fromcode += 4;
404 else if (ascncasecmp(fromcode, "iso", 3) == 0)
405 fromcode += 3;
406 if (*tocode == '\0' || *fromcode == '\0')
407 return (iconv_t) -1;
408 if ((id = iconv_open(tocode, fromcode)) != (iconv_t)-1)
409 return id;
411 * Solaris prefers upper-case charset names. Don't ask...
413 t = salloc(strlen(tocode) + 1);
414 uppercopy(t, tocode);
415 f = salloc(strlen(fromcode) + 1);
416 uppercopy(f, fromcode);
417 if ((id = iconv_open(t, f)) != (iconv_t)-1)
418 return id;
420 * Strip dashes for UnixWare.
422 stripdash(t);
423 stripdash(f);
424 if ((id = iconv_open(t, f)) != (iconv_t)-1)
425 return id;
427 * Add your vendor's sillynesses here.
430 * If the encoding names are equal at this point, they
431 * are just not understood by iconv(), and we cannot
432 * sensibly use it in any way. We do not perform this
433 * as an optimization above since iconv() can otherwise
434 * be used to check the validity of the input even with
435 * identical encoding names.
437 if (strcmp(t, f) == 0)
438 errno = 0;
439 return (iconv_t)-1;
443 * Fault-tolerant iconv() function.
445 static size_t
446 iconv_ft(iconv_t cd, char **inb, size_t *inbleft, char **outb, size_t *outbleft)
448 size_t sz = 0;
450 while ((sz = iconv(cd, inb, inbleft, outb, outbleft)) == (size_t)-1
451 && (errno == EILSEQ || errno == EINVAL)) {
452 if (*inbleft > 0) {
453 (*inb)++;
454 (*inbleft)--;
455 } else {
456 **outb = '\0';
457 break;
459 if (*outbleft > 0) {
460 *(*outb)++ = '?';
461 (*outbleft)--;
462 } else {
463 **outb = '\0';
464 break;
467 return sz;
471 * Print an error because of an invalid character sequence.
473 /*ARGSUSED*/
474 static void
475 invalid_seq(int c)
477 (void)c;
478 /*fprintf(stderr, "iconv: cannot convert %c\n", c);*/
480 #endif /* HAVE_ICONV */
482 static int
483 is_this_enc(const char *line, const char *encoding)
485 int quoted = 0, c;
487 if (*line == '"')
488 quoted = 1, line++;
489 while (*line && *encoding)
490 if (c = *line++, lowerconv(c) != *encoding++)
491 return 0;
492 if (quoted && *line == '"')
493 return 1;
494 if (*line == '\0' || whitechar(*line & 0377))
495 return 1;
496 return 0;
500 * Get the mime encoding from a Content-Transfer-Encoding header field.
502 enum mimeenc
503 mime_getenc(char *p)
505 if (is_this_enc(p, "7bit"))
506 return MIME_7B;
507 if (is_this_enc(p, "8bit"))
508 return MIME_8B;
509 if (is_this_enc(p, "base64"))
510 return MIME_B64;
511 if (is_this_enc(p, "binary"))
512 return MIME_BIN;
513 if (is_this_enc(p, "quoted-printable"))
514 return MIME_QP;
515 return MIME_NONE;
519 * Get the mime content from a Content-Type header field, other parameters
520 * already stripped.
522 int
523 mime_getcontent(char *s)
525 if (strchr(s, '/') == NULL) /* for compatibility with non-MIME */
526 return MIME_TEXT;
527 if (asccasecmp(s, "text/plain") == 0)
528 return MIME_TEXT_PLAIN;
529 if (asccasecmp(s, "text/html") == 0)
530 return MIME_TEXT_HTML;
531 if (ascncasecmp(s, "text/", 5) == 0)
532 return MIME_TEXT;
533 if (asccasecmp(s, "message/rfc822") == 0)
534 return MIME_822;
535 if (ascncasecmp(s, "message/", 8) == 0)
536 return MIME_MESSAGE;
537 if (asccasecmp(s, "multipart/alternative") == 0)
538 return MIME_ALTERNATIVE;
539 if (asccasecmp(s, "multipart/digest") == 0)
540 return MIME_DIGEST;
541 if (ascncasecmp(s, "multipart/", 10) == 0)
542 return MIME_MULTI;
543 if (asccasecmp(s, "application/x-pkcs7-mime") == 0 ||
544 asccasecmp(s, "application/pkcs7-mime") == 0)
545 return MIME_PKCS7;
546 return MIME_UNKNOWN;
550 * Get a mime style parameter from a header line.
552 char *
553 mime_getparam(char *param, char *h)
555 char *p = h, *q, *r;
556 int c;
557 size_t sz;
559 sz = strlen(param);
560 if (!whitechar(*p & 0377)) {
561 c = '\0';
562 while (*p && (*p != ';' || c == '\\')) {
563 c = c == '\\' ? '\0' : *p;
564 p++;
566 if (*p++ == '\0')
567 return NULL;
569 for (;;) {
570 while (whitechar(*p & 0377))
571 p++;
572 if (ascncasecmp(p, param, sz) == 0) {
573 p += sz;
574 while (whitechar(*p & 0377))
575 p++;
576 if (*p++ == '=')
577 break;
579 c = '\0';
580 while (*p && (*p != ';' || c == '\\')) {
581 if (*p == '"' && c != '\\') {
582 p++;
583 while (*p && (*p != '"' || c == '\\')) {
584 c = c == '\\' ? '\0' : *p;
585 p++;
587 p++;
588 } else {
589 c = c == '\\' ? '\0' : *p;
590 p++;
593 if (*p++ == '\0')
594 return NULL;
596 while (whitechar(*p & 0377))
597 p++;
598 q = p;
599 c = '\0';
600 if (*p == '"') {
601 p++;
602 if ((q = strchr(p, '"')) == NULL)
603 return NULL;
604 } else {
605 q = p;
606 while (*q && !whitechar(*q & 0377) && *q != ';')
607 q++;
609 sz = q - p;
610 r = salloc(q - p + 1);
611 memcpy(r, p, sz);
612 *(r + sz) = '\0';
613 return r;
617 * Get the boundary out of a Content-Type: multipart/xyz header field.
619 char *
620 mime_getboundary(char *h)
622 char *p, *q;
623 size_t sz;
625 if ((p = mime_getparam("boundary", h)) == NULL)
626 return NULL;
627 sz = strlen(p);
628 q = salloc(sz + 3);
629 memcpy(q, "--", 2);
630 memcpy(q + 2, p, sz);
631 *(q + sz + 2) = '\0';
632 return q;
636 * Get a line like "text/html html" and look if x matches the extension.
638 static char *
639 mime_tline(char *x, char *l)
641 char *type, *n;
642 int match = 0;
644 if ((*l & 0200) || alphachar(*l & 0377) == 0)
645 return NULL;
646 type = l;
647 while (blankchar(*l & 0377) == 0 && *l != '\0')
648 l++;
649 if (*l == '\0')
650 return NULL;
651 *l++ = '\0';
652 while (blankchar(*l & 0377) != 0 && *l != '\0')
653 l++;
654 if (*l == '\0')
655 return NULL;
656 while (*l != '\0') {
657 n = l;
658 while (whitechar(*l & 0377) == 0 && *l != '\0')
659 l++;
660 if (*l != '\0')
661 *l++ = '\0';
662 if (strcmp(x, n) == 0) {
663 match = 1;
664 break;
666 while (whitechar(*l & 0377) != 0 && *l != '\0')
667 l++;
669 if (match != 0) {
670 n = salloc(strlen(type) + 1);
671 strcpy(n, type);
672 return n;
674 return NULL;
678 * Check the given MIME type file for extension ext.
680 static char *
681 mime_type(char *ext, char *filename)
683 FILE *f;
684 char *line = NULL;
685 size_t linesize = 0;
686 char *type = NULL;
688 if ((f = Fopen(filename, "r")) == NULL)
689 return NULL;
690 while (fgetline(&line, &linesize, NULL, NULL, f, 0)) {
691 if ((type = mime_tline(ext, line)) != NULL)
692 break;
694 Fclose(f);
695 if (line)
696 free(line);
697 return type;
701 * Return the Content-Type matching the extension of name.
703 char *
704 mime_filecontent(char *name)
706 char *ext, *content;
708 if ((ext = strrchr(name, '.')) == NULL || *++ext == '\0')
709 return NULL;
710 if ((content = mime_type(ext, expand(mimetypes_user))) != NULL)
711 return content;
712 if ((content = mime_type(ext, mimetypes_world)) != NULL)
713 return content;
714 return NULL;
718 * Check file contents.
720 static enum mimeclean
721 mime_isclean(FILE *f)
723 long initial_pos;
724 unsigned curlen = 1, maxlen = 0, limit = 950;
725 enum mimeclean isclean = 0;
726 char *cp;
727 int c = EOF, lastc;
729 initial_pos = ftell(f);
730 do {
731 lastc = c;
732 c = getc(f);
733 curlen++;
734 if (c == '\n' || c == EOF) {
736 * RFC 821 imposes a maximum line length of 1000
737 * characters including the terminating CRLF
738 * sequence. The configurable limit must not
739 * exceed that including a safety zone.
741 if (curlen > maxlen)
742 maxlen = curlen;
743 curlen = 1;
744 } else if (c & 0200) {
745 isclean |= MIME_HIGHBIT;
746 } else if (c == '\0') {
747 isclean |= MIME_HASNUL;
748 break;
749 } else if ((c < 040 && (c != '\t' && c != '\f')) || c == 0177) {
750 isclean |= MIME_CTRLCHAR;
752 } while (c != EOF);
753 if (lastc != '\n')
754 isclean |= MIME_NOTERMNL;
755 clearerr(f);
756 fseek(f, initial_pos, SEEK_SET);
757 if ((cp = value("maximum-unencoded-line-length")) != NULL)
758 limit = (unsigned)atoi(cp);
759 if (limit > 950)
760 limit = 950;
761 if (maxlen > limit)
762 isclean |= MIME_LONGLINES;
763 return isclean;
767 * Get the conversion that matches the encoding specified in the environment.
769 static enum conversion
770 gettextconversion(void)
772 char *p;
773 int convert;
775 if ((p = value("encoding")) == NULL)
776 return CONV_8BIT;
777 if (equal(p, "quoted-printable"))
778 convert = CONV_TOQP;
779 else if (equal(p, "8bit"))
780 convert = CONV_8BIT;
781 else {
782 fprintf(stderr, catgets(catd, CATSET, 177,
783 "Warning: invalid encoding %s, using 8bit\n"), p);
784 convert = CONV_8BIT;
786 return convert;
790 get_mime_convert(FILE *fp, char **contenttype, char **charset,
791 enum mimeclean *isclean, int dosign)
793 int convert;
795 *isclean = mime_isclean(fp);
796 if (*isclean & MIME_HASNUL ||
797 (*contenttype &&
798 ascncasecmp(*contenttype, "text/", 5))) {
799 convert = CONV_TOB64;
800 if (*contenttype == NULL ||
801 ascncasecmp(*contenttype, "text/", 5) == 0)
802 *contenttype = "application/octet-stream";
803 *charset = NULL;
804 } else if (*isclean & (MIME_LONGLINES|MIME_CTRLCHAR|MIME_NOTERMNL) ||
805 dosign)
806 convert = CONV_TOQP;
807 else if (*isclean & MIME_HIGHBIT)
808 convert = gettextconversion();
809 else
810 convert = CONV_7BIT;
811 if (*contenttype == NULL ||
812 ascncasecmp(*contenttype, "text/", 5) == 0) {
813 *charset = getcharset(*isclean);
814 if (wantcharset == (char *)-1) {
815 *contenttype = "application/octet-stream";
816 *charset = NULL;
817 } if (*isclean & MIME_CTRLCHAR) {
818 convert = CONV_TOB64;
820 * RFC 2046 forbids control characters other than
821 * ^I or ^L in text/plain bodies. However, some
822 * obscure character sets actually contain these
823 * characters, so the content type can be set.
825 if ((*contenttype = value("contenttype-cntrl")) == NULL)
826 *contenttype = "application/octet-stream";
827 } else if (*contenttype == NULL)
828 *contenttype = "text/plain";
830 return convert;
834 * Convert c to a hexadecimal character string and store it in hex.
836 static char *
837 ctohex(int c, char *hex)
839 unsigned char d;
841 hex[2] = '\0';
842 d = c % 16;
843 hex[1] = basetable[d];
844 if (c > d)
845 hex[0] = basetable[(c - d) / 16];
846 else
847 hex[0] = basetable[0];
848 return hex;
852 * Write to a file converting to quoted-printable.
853 * The mustquote function determines whether a character must be quoted.
855 static size_t
856 mime_write_toqp(struct str *in, FILE *fo, int (*mustquote)(int))
858 char *p, *upper, *h, hex[3];
859 int l;
860 size_t sz;
862 sz = in->l;
863 upper = in->s + in->l;
864 for (p = in->s, l = 0; p < upper; p++) {
865 if (mustquote(*p&0377) ||
866 (p < upper-1 && p[1] == '\n' &&
867 blankchar(p[0]&0377)) ||
868 (p < upper-4 && l == 0 &&
869 p[0] == 'F' && p[1] == 'r' &&
870 p[2] == 'o' && p[3] == 'm') ||
871 (*p == '.' && l == 0 && p < upper-1 &&
872 p[1] == '\n')) {
873 if (l >= 69) {
874 sz += 2;
875 fwrite("=\n", sizeof (char), 2, fo);
876 l = 0;
878 sz += 2;
879 putc('=', fo);
880 h = ctohex(*p&0377, hex);
881 fwrite(h, sizeof *h, 2, fo);
882 l += 3;
883 } else {
884 if (*p == '\n')
885 l = 0;
886 else if (l >= 71) {
887 sz += 2;
888 fwrite("=\n", sizeof (char), 2, fo);
889 l = 0;
891 putc(*p, fo);
892 l++;
895 return sz;
899 * Write to a stringstruct converting to quoted-printable.
900 * The mustquote function determines whether a character must be quoted.
902 static void
903 mime_str_toqp(struct str *in, struct str *out, int (*mustquote)(int), int inhdr)
905 char *p, *q, *upper;
907 out->s = smalloc(in->l * 3 + 1);
908 q = out->s;
909 out->l = in->l;
910 upper = in->s + in->l;
911 for (p = in->s; p < upper; p++) {
912 if (mustquote(*p&0377) || (p+1 < upper && *(p + 1) == '\n' &&
913 blankchar(*p & 0377))) {
914 if (inhdr && *p == ' ') {
915 *q++ = '_';
916 } else {
917 out->l += 2;
918 *q++ = '=';
919 ctohex(*p&0377, q);
920 q += 2;
922 } else {
923 *q++ = *p;
926 *q = '\0';
930 * Write to a stringstruct converting from quoted-printable.
932 static void
933 mime_fromqp(struct str *in, struct str *out, int ishdr)
935 char *p, *q, *upper;
936 char quote[4];
938 out->l = in->l;
939 out->s = smalloc(out->l + 1);
940 upper = in->s + in->l;
941 for (p = in->s, q = out->s; p < upper; p++) {
942 if (*p == '=') {
943 do {
944 p++;
945 out->l--;
946 } while (blankchar(*p & 0377) && p < upper);
947 if (p == upper)
948 break;
949 if (*p == '\n') {
950 out->l--;
951 continue;
953 if (p + 1 >= upper)
954 break;
955 quote[0] = *p++;
956 quote[1] = *p;
957 quote[2] = '\0';
958 *q = (char)strtol(quote, NULL, 16);
959 q++;
960 out->l--;
961 } else if (ishdr && *p == '_')
962 *q++ = ' ';
963 else
964 *q++ = *p;
966 return;
969 #define mime_fromhdr_inc(inc) { \
970 size_t diff = q - out->s; \
971 out->s = srealloc(out->s, (maxstor += inc) + 1); \
972 q = &(out->s)[diff]; \
975 * Convert header fields from RFC 1522 format
977 void
978 mime_fromhdr(struct str *in, struct str *out, enum tdflags flags)
980 char *p, *q, *op, *upper, *cs, *cbeg, *tcs, *lastwordend = NULL;
981 struct str cin, cout;
982 int convert;
983 size_t maxstor, lastoutl = 0;
984 #ifdef HAVE_ICONV
985 iconv_t fhicd = (iconv_t)-1;
986 #endif
988 tcs = gettcharset();
989 maxstor = in->l;
990 out->s = smalloc(maxstor + 1);
991 out->l = 0;
992 upper = in->s + in->l;
993 for (p = in->s, q = out->s; p < upper; p++) {
994 op = p;
995 if (*p == '=' && *(p + 1) == '?') {
996 p += 2;
997 cbeg = p;
998 while (p < upper && *p != '?')
999 p++; /* strip charset */
1000 if (p >= upper)
1001 goto notmime;
1002 cs = salloc(++p - cbeg);
1003 memcpy(cs, cbeg, p - cbeg - 1);
1004 cs[p - cbeg - 1] = '\0';
1005 #ifdef HAVE_ICONV
1006 if (fhicd != (iconv_t)-1)
1007 iconv_close(fhicd);
1008 if (strcmp(cs, tcs))
1009 fhicd = iconv_open_ft(tcs, cs);
1010 else
1011 fhicd = (iconv_t)-1;
1012 #endif
1013 switch (*p) {
1014 case 'B': case 'b':
1015 convert = CONV_FROMB64;
1016 break;
1017 case 'Q': case 'q':
1018 convert = CONV_FROMQP;
1019 break;
1020 default: /* invalid, ignore */
1021 goto notmime;
1023 if (*++p != '?')
1024 goto notmime;
1025 cin.s = ++p;
1026 cin.l = 1;
1027 for (;;) {
1028 if (p == upper)
1029 goto fromhdr_end;
1030 if (*p++ == '?' && *p == '=')
1031 break;
1032 cin.l++;
1034 cin.l--;
1035 switch (convert) {
1036 case CONV_FROMB64:
1037 mime_fromb64(&cin, &cout, 1);
1038 break;
1039 case CONV_FROMQP:
1040 mime_fromqp(&cin, &cout, 1);
1041 break;
1043 if (lastwordend) {
1044 q = lastwordend;
1045 out->l = lastoutl;
1047 #ifdef HAVE_ICONV
1048 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
1049 char *iptr, *mptr, *nptr, *uptr;
1050 size_t inleft, outleft;
1052 again: inleft = cout.l;
1053 outleft = maxstor - out->l;
1054 mptr = nptr = q;
1055 uptr = nptr + outleft;
1056 iptr = cout.s;
1057 if (iconv_ft(fhicd, &iptr, &inleft,
1058 &nptr, &outleft) == (size_t)-1 &&
1059 errno == E2BIG) {
1060 iconv(fhicd, NULL, NULL, NULL, NULL);
1061 mime_fromhdr_inc(inleft);
1062 goto again;
1065 * For state-dependent encodings,
1066 * reset the state here, assuming
1067 * that states are restricted to
1068 * single encoded-word parts.
1070 while (iconv(fhicd, NULL, NULL,
1071 &nptr, &outleft) == (size_t)-1 &&
1072 errno == E2BIG)
1073 mime_fromhdr_inc(16);
1074 out->l += uptr - mptr - outleft;
1075 q += uptr - mptr - outleft;
1076 } else {
1077 #endif
1078 while (cout.l > maxstor - out->l)
1079 mime_fromhdr_inc(cout.l -
1080 (maxstor - out->l));
1081 memcpy(q, cout.s, cout.l);
1082 q += cout.l;
1083 out->l += cout.l;
1084 #ifdef HAVE_ICONV
1086 #endif
1087 free(cout.s);
1088 lastwordend = q;
1089 lastoutl = out->l;
1090 } else {
1091 notmime:
1092 p = op;
1093 while (out->l >= maxstor)
1094 mime_fromhdr_inc(16);
1095 *q++ = *p;
1096 out->l++;
1097 if (!blankchar(*p&0377))
1098 lastwordend = NULL;
1101 fromhdr_end:
1102 *q = '\0';
1103 if (flags & TD_ISPR) {
1104 struct str new;
1105 makeprint(out, &new);
1106 free(out->s);
1107 *out = new;
1109 if (flags & TD_DELCTRL)
1110 out->l = delctrl(out->s, out->l);
1111 #ifdef HAVE_ICONV
1112 if (fhicd != (iconv_t)-1)
1113 iconv_close(fhicd);
1114 #endif
1115 return;
1119 * Convert header fields to RFC 1522 format and write to the file fo.
1121 static size_t
1122 mime_write_tohdr(struct str *in, FILE *fo)
1124 char *upper, *wbeg, *wend, *charset, *lastwordend = NULL, *lastspc, b,
1125 *charset7;
1126 struct str cin, cout;
1127 size_t sz = 0, col = 0, wr, charsetlen, charset7len;
1128 int quoteany, mustquote, broken,
1129 maxcol = 65 /* there is the header field's name, too */;
1131 upper = in->s + in->l;
1132 charset = getcharset(MIME_HIGHBIT);
1133 if ((charset7 = value("charset7")) == NULL)
1134 charset7 = us_ascii;
1135 charsetlen = strlen(charset);
1136 charset7len = strlen(charset7);
1137 charsetlen = smax(charsetlen, charset7len);
1138 b = 0;
1139 for (wbeg = in->s, quoteany = 0; wbeg < upper; wbeg++) {
1140 b |= *wbeg;
1141 if (mustquote_hdr(wbeg, wbeg == in->s, wbeg == &upper[-1]))
1142 quoteany++;
1144 if (2u * quoteany > in->l) {
1146 * Print the entire field in base64.
1148 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
1149 wend = upper;
1150 cin.s = wbeg;
1151 for (;;) {
1152 cin.l = wend - wbeg;
1153 if (cin.l * 4/3 + 7 + charsetlen
1154 < maxcol - col) {
1155 fprintf(fo, "=?%s?B?",
1156 b&0200 ? charset : charset7);
1157 wr = mime_write_tob64(&cin, fo, 1);
1158 fwrite("?=", sizeof (char), 2, fo);
1159 wr += 7 + charsetlen;
1160 sz += wr, col += wr;
1161 if (wend < upper) {
1162 fwrite("\n ", sizeof (char),
1163 2, fo);
1164 sz += 2;
1165 col = 0;
1166 maxcol = 76;
1168 break;
1169 } else {
1170 if (col) {
1171 fprintf(fo, "\n ");
1172 sz += 2;
1173 col = 0;
1174 maxcol = 76;
1175 } else
1176 wend -= 4;
1180 } else {
1182 * Print the field word-wise in quoted-printable.
1184 broken = 0;
1185 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
1186 lastspc = NULL;
1187 while (wbeg < upper && whitechar(*wbeg & 0377)) {
1188 lastspc = lastspc ? lastspc : wbeg;
1189 wbeg++;
1190 col++;
1191 broken = 0;
1193 if (wbeg == upper) {
1194 if (lastspc)
1195 while (lastspc < wbeg) {
1196 putc(*lastspc&0377, fo);
1197 lastspc++,
1198 sz++;
1200 break;
1202 mustquote = 0;
1203 b = 0;
1204 for (wend = wbeg;
1205 wend < upper && !whitechar(*wend & 0377);
1206 wend++) {
1207 b |= *wend;
1208 if (mustquote_hdr(wend, wend == wbeg,
1209 wbeg == &upper[-1]))
1210 mustquote++;
1212 if (mustquote || broken ||
1213 ((wend - wbeg) >= 74 && quoteany)) {
1214 for (;;) {
1215 cin.s = lastwordend ? lastwordend :
1216 wbeg;
1217 cin.l = wend - cin.s;
1218 mime_str_toqp(&cin, &cout,
1219 mustquote_inhdrq, 1);
1220 if ((wr = cout.l + charsetlen + 7)
1221 < maxcol - col) {
1222 if (lastspc)
1223 while (lastspc < wbeg) {
1224 putc(*lastspc
1225 &0377,
1226 fo);
1227 lastspc++,
1228 sz++;
1230 fprintf(fo, "=?%s?Q?", b&0200 ?
1231 charset : charset7);
1232 fwrite(cout.s, sizeof *cout.s,
1233 cout.l, fo);
1234 fwrite("?=", 1, 2, fo);
1235 sz += wr, col += wr;
1236 free(cout.s);
1237 break;
1238 } else {
1239 broken = 1;
1240 if (col) {
1241 putc('\n', fo);
1242 sz++;
1243 col = 0;
1244 maxcol = 76;
1245 if (lastspc == NULL) {
1246 putc(' ', fo);
1247 sz++;
1248 maxcol--;
1249 } else
1250 maxcol -= wbeg -
1251 lastspc;
1252 } else {
1253 wend -= 4;
1255 free(cout.s);
1258 lastwordend = wend;
1259 } else {
1260 if (col &&
1261 (size_t)(wend - wbeg) > maxcol - col) {
1262 putc('\n', fo);
1263 sz++;
1264 col = 0;
1265 maxcol = 76;
1266 if (lastspc == NULL) {
1267 putc(' ', fo);
1268 sz++;
1269 maxcol--;
1270 } else
1271 maxcol -= wbeg - lastspc;
1273 if (lastspc)
1274 while (lastspc < wbeg) {
1275 putc(*lastspc&0377, fo);
1276 lastspc++, sz++;
1278 wr = fwrite(wbeg, sizeof *wbeg,
1279 wend - wbeg, fo);
1280 sz += wr, col += wr;
1281 lastwordend = NULL;
1285 return sz;
1289 * Write len characters of the passed string to the passed file,
1290 * doing charset and header conversion.
1292 static size_t
1293 convhdra(char *str, size_t len, FILE *fp)
1295 #ifdef HAVE_ICONV
1296 char *ip, *op;
1297 size_t isz, osz;
1298 #endif
1299 struct str cin;
1300 size_t cbufsz;
1301 char *cbuf;
1302 size_t sz;
1304 cbuf = ac_alloc(cbufsz = 1);
1305 #ifdef HAVE_ICONV
1306 if (iconvd == (iconv_t)-1) {
1307 #endif
1308 cin.s = str;
1309 cin.l = len;
1310 #ifdef HAVE_ICONV
1311 } else {
1312 again: ip = str;
1313 isz = len;
1314 op = cbuf;
1315 osz = cbufsz;
1316 if (iconv(iconvd, &ip, &isz, &op, &osz) == (size_t)-1) {
1317 if (errno != E2BIG) {
1318 ac_free(cbuf);
1319 return 0;
1321 cbuf = ac_alloc(cbufsz += isz);
1322 goto again;
1324 cin.s = cbuf;
1325 cin.l = cbufsz - osz;
1327 #endif /* HAVE_ICONV */
1328 sz = mime_write_tohdr(&cin, fp);
1329 ac_free(cbuf);
1330 return sz;
1335 * Write an address to a header field.
1337 static size_t
1338 mime_write_tohdr_a(struct str *in, FILE *f)
1340 char *cp, *lastcp;
1341 size_t sz = 0;
1343 in->s[in->l] = '\0';
1344 lastcp = in->s;
1345 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
1346 sz += convhdra(lastcp, cp - lastcp, f);
1347 lastcp = cp;
1348 } else
1349 cp = in->s;
1350 for ( ; *cp; cp++) {
1351 switch (*cp) {
1352 case '(':
1353 sz += fwrite(lastcp, 1, cp - lastcp + 1, f);
1354 lastcp = ++cp;
1355 cp = skip_comment(cp);
1356 if (--cp > lastcp)
1357 sz += convhdra(lastcp, cp - lastcp, f);
1358 lastcp = cp;
1359 break;
1360 case '"':
1361 while (*cp) {
1362 if (*++cp == '"')
1363 break;
1364 if (*cp == '\\' && cp[1])
1365 cp++;
1367 break;
1370 if (cp > lastcp)
1371 sz += fwrite(lastcp, 1, cp - lastcp, f);
1372 return sz;
1375 static void
1376 addstr(char **buf, size_t *sz, size_t *pos, char *str, size_t len)
1378 *buf = srealloc(*buf, *sz += len);
1379 memcpy(&(*buf)[*pos], str, len);
1380 *pos += len;
1383 static void
1384 addconv(char **buf, size_t *sz, size_t *pos, char *str, size_t len)
1386 struct str in, out;
1388 in.s = str;
1389 in.l = len;
1390 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
1391 addstr(buf, sz, pos, out.s, out.l);
1392 free(out.s);
1396 * Interpret MIME strings in parts of an address field.
1398 char *
1399 mime_fromaddr(char *name)
1401 char *cp, *lastcp;
1402 char *res = NULL;
1403 size_t ressz = 1, rescur = 0;
1405 if (name == NULL || *name == '\0')
1406 return name;
1407 if ((cp = routeaddr(name)) != NULL && cp > name) {
1408 addconv(&res, &ressz, &rescur, name, cp - name);
1409 lastcp = cp;
1410 } else
1411 cp = lastcp = name;
1412 for ( ; *cp; cp++) {
1413 switch (*cp) {
1414 case '(':
1415 addstr(&res, &ressz, &rescur, lastcp, cp - lastcp + 1);
1416 lastcp = ++cp;
1417 cp = skip_comment(cp);
1418 if (--cp > lastcp)
1419 addconv(&res, &ressz, &rescur, lastcp,
1420 cp - lastcp);
1421 lastcp = cp;
1422 break;
1423 case '"':
1424 while (*cp) {
1425 if (*++cp == '"')
1426 break;
1427 if (*cp == '\\' && cp[1])
1428 cp++;
1430 break;
1433 if (cp > lastcp)
1434 addstr(&res, &ressz, &rescur, lastcp, cp - lastcp);
1435 res[rescur] = '\0';
1436 cp = savestr(res);
1437 free(res);
1438 return cp;
1442 * fwrite whilst adding prefix, if present.
1444 size_t
1445 prefixwrite(void *ptr, size_t size, size_t nmemb, FILE *f,
1446 char *prefix, size_t prefixlen)
1448 static FILE *lastf;
1449 static char lastc = '\n';
1450 size_t lpref, i, qfold = 0, lnlen = 0, rsz = size * nmemb, wsz = 0;
1451 char *p, *maxp, c;
1453 if (rsz == 0)
1454 return 0;
1455 if (prefix == NULL) {
1456 lastf = f;
1457 lastc = ((char *)ptr)[rsz - 1];
1458 return fwrite(ptr, 1, rsz, f);
1461 if ((p = value("quote-fold")) != NULL) {
1462 qfold = (size_t)strtol(p, NULL, 10);
1463 if (qfold < prefixlen + 4)
1464 qfold = prefixlen + 4;
1465 --qfold; /* The newline escape */
1468 if (f != lastf || lastc == '\n') {
1469 wsz += fwrite(prefix, sizeof *prefix, prefixlen, f);
1470 lnlen = prefixlen;
1472 lastf = f;
1474 p = ptr;
1475 maxp = p + rsz;
1477 if (! qfold) {
1478 for (;;) {
1479 c = *p++;
1480 putc(c, f);
1481 wsz++;
1482 if (p == maxp)
1483 break;
1484 if (c != '\n')
1485 continue;
1486 wsz += fwrite(prefix, sizeof *prefix, prefixlen, f);
1488 } else {
1489 for (;;) {
1491 * After writing a real newline followed by our prefix,
1492 * compress the quoted prefixes
1494 for (lpref = 0; p != maxp;) {
1495 /* (c: keep cc happy) */
1496 for (c = i = 0; p + i < maxp;) {
1497 c = p[i++];
1498 if (blankspacechar(c)) /* XXX U+A0+ */
1499 continue;
1500 if (! ISQUOTE(c))
1501 goto jquoteok;
1502 break;
1504 p += i;
1505 ++lpref;
1506 putc(c, f);
1507 ++wsz;
1509 jquoteok: lnlen += lpref;
1511 jsoftnl: /*
1512 * Search forward until either *quote-fold* or NL.
1513 * In the former case try to break at whitespace,
1514 * but only if that lies in the 2nd half of the data
1516 for (c = rsz = i = 0; p + i < maxp;) {
1517 c = p[i++];
1518 if (c == '\n')
1519 break;
1520 if (spacechar(c))
1521 rsz = i;
1522 if (lnlen + i >= qfold) {
1523 c = 0;
1524 if (rsz > qfold >> 1)
1525 i = rsz;
1526 break;
1530 if (i > 0) {
1531 wsz += fwrite(p, sizeof *p, i, f);
1532 p += i;
1534 if (p >= maxp)
1535 break;
1537 if (c != '\n') {
1538 putc('\\', f);
1539 putc('\n', f);
1540 wsz += 2;
1543 wsz += fwrite(prefix, sizeof *prefix, prefixlen, f);
1544 lnlen = prefixlen;
1545 if (c == '\n')
1546 continue;
1548 if ((i = lpref)) {
1549 for (; i > 0; ++wsz, ++lnlen, --i)
1550 (void)putc('.', f);
1551 (void)putc(' ', f);
1552 ++wsz;
1553 ++lnlen;
1555 goto jsoftnl;
1559 lastc = p[-1];
1560 return wsz;
1564 * fwrite while checking for displayability.
1566 static size_t
1567 fwrite_td(void *ptr, size_t size, size_t nmemb, FILE *f, enum tdflags flags,
1568 char *prefix, size_t prefixlen)
1570 char *upper;
1571 size_t sz, csize;
1572 #ifdef HAVE_ICONV
1573 char *iptr, *nptr;
1574 size_t inleft, outleft;
1575 #endif
1576 char *mptr, *xmptr, *mlptr = NULL;
1577 size_t mptrsz;
1579 csize = size * nmemb;
1580 mptrsz = csize;
1581 mptr = xmptr = ac_alloc(mptrsz + 1);
1582 #ifdef HAVE_ICONV
1583 if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
1584 again: inleft = csize;
1585 outleft = mptrsz;
1586 nptr = mptr;
1587 iptr = ptr;
1588 if (iconv_ft(iconvd, &iptr, &inleft, &nptr, &outleft) ==
1589 (size_t)-1 &&
1590 errno == E2BIG) {
1591 iconv(iconvd, NULL, NULL, NULL, NULL);
1592 ac_free(mptr);
1593 mptrsz += inleft;
1594 mptr = ac_alloc(mptrsz + 1);
1595 goto again;
1597 nmemb = mptrsz - outleft;
1598 size = sizeof (char);
1599 ptr = mptr;
1600 csize = size * nmemb;
1601 } else
1602 #endif
1604 memcpy(mptr, ptr, csize);
1606 upper = mptr + csize;
1607 *upper = '\0';
1608 if (flags & TD_ISPR) {
1609 struct str in, out;
1610 in.s = mptr;
1611 in.l = csize;
1612 makeprint(&in, &out);
1613 mptr = mlptr = out.s;
1614 csize = out.l;
1616 if (flags & TD_DELCTRL)
1617 csize = delctrl(mptr, csize);
1618 sz = prefixwrite(mptr, sizeof *mptr, csize, f, prefix, prefixlen);
1619 ac_free(xmptr);
1620 free(mlptr);
1621 return sz;
1625 * fwrite performing the given MIME conversion.
1627 size_t
1628 mime_write(void *ptr, size_t size, FILE *f,
1629 enum conversion convert, enum tdflags dflags,
1630 char *prefix, size_t prefixlen,
1631 char **restp, size_t *restsizep)
1633 struct str in, out;
1634 size_t sz, csize;
1635 int is_text = 0;
1636 #ifdef HAVE_ICONV
1637 char mptr[LINESIZE * 6];
1638 char *iptr, *nptr;
1639 size_t inleft, outleft;
1640 #endif
1642 if (size == 0)
1643 return 0;
1644 csize = size;
1645 #ifdef HAVE_ICONV
1646 if (csize < sizeof mptr && (dflags & TD_ICONV)
1647 && iconvd != (iconv_t)-1
1648 && (convert == CONV_TOQP || convert == CONV_8BIT ||
1649 convert == CONV_TOB64 ||
1650 convert == CONV_TOHDR)) {
1651 inleft = csize;
1652 outleft = sizeof mptr;
1653 nptr = mptr;
1654 iptr = ptr;
1655 if (iconv(iconvd, &iptr, &inleft,
1656 &nptr, &outleft) != (size_t)-1) {
1657 in.l = sizeof mptr - outleft;
1658 in.s = mptr;
1659 } else {
1660 if (errno == EILSEQ || errno == EINVAL)
1661 invalid_seq(*iptr);
1662 return 0;
1664 } else {
1665 #endif
1666 in.s = ptr;
1667 in.l = csize;
1668 #ifdef HAVE_ICONV
1670 #endif
1671 switch (convert) {
1672 case CONV_FROMQP:
1673 mime_fromqp(&in, &out, 0);
1674 sz = fwrite_td(out.s, sizeof *out.s, out.l, f, dflags,
1675 prefix, prefixlen);
1676 free(out.s);
1677 break;
1678 case CONV_TOQP:
1679 sz = mime_write_toqp(&in, f, mustquote_body);
1680 break;
1681 case CONV_8BIT:
1682 sz = prefixwrite(in.s, sizeof *in.s, in.l, f,
1683 prefix, prefixlen);
1684 break;
1685 case CONV_FROMB64_T:
1686 is_text = 1;
1687 /*FALLTHROUGH*/
1688 case CONV_FROMB64:
1689 mime_fromb64_b(&in, &out, is_text, f);
1690 if (is_text && out.s[out.l-1] != '\n' && restp && restsizep) {
1691 *restp = ptr;
1692 *restsizep = size;
1693 sz = 0;
1694 } else {
1695 sz = fwrite_td(out.s, sizeof *out.s, out.l, f, dflags,
1696 prefix, prefixlen);
1698 free(out.s);
1699 break;
1700 case CONV_TOB64:
1701 sz = mime_write_tob64(&in, f, 0);
1702 break;
1703 case CONV_FROMHDR:
1704 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
1705 sz = fwrite_td(out.s, sizeof *out.s, out.l, f,
1706 dflags&TD_DELCTRL, prefix, prefixlen);
1707 free(out.s);
1708 break;
1709 case CONV_TOHDR:
1710 sz = mime_write_tohdr(&in, f);
1711 break;
1712 case CONV_TOHDR_A:
1713 sz = mime_write_tohdr_a(&in, f);
1714 break;
1715 default:
1716 sz = fwrite_td(in.s, sizeof *in.s, in.l, f, dflags,
1717 prefix, prefixlen);
1719 return sz;