1 /*        $NetBSD: mime_codecs.c,v 1.12 2019/10/24 18:18:00 kamil Exp $         */
2 
3 /*-
4  * Copyright (c) 2006 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Anon Ymous.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*
33  * This module contains all mime related codecs.  Typically there are
34  * two versions: one operating on buffers and one operating on files.
35  * All exported routines have a "mime_" prefix.  The file oriented
36  * routines have a "mime_f" prefix replacing the "mime_" prefix of the
37  * equivalent buffer based version.
38  *
39  * The file based API should be:
40  *
41  *   mime_f<name>_{encode,decode}(FILE *in, FILE *out, void *cookie)
42  *
43  * XXX - currently this naming convention has not been adheared to.
44  *
45  * where the cookie is a generic way to pass arguments to the routine.
46  * This way these routines can be run by run_function() in mime.c.
47  *
48  * The buffer based API is not as rigid.
49  */
50 
51 #ifdef MIME_SUPPORT
52 
53 #include <sys/cdefs.h>
54 #ifndef __lint__
55 __RCSID("$NetBSD: mime_codecs.c,v 1.12 2019/10/24 18:18:00 kamil Exp $");
56 #endif /* not __lint__ */
57 
58 #include <assert.h>
59 #include <iconv.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <util.h>
63 
64 #include "def.h"
65 #include "extern.h"
66 #include "mime_codecs.h"
67 
68 
69 #ifdef CHARSET_SUPPORT
70 /************************************************************************
71  * Core character set conversion routines.
72  *
73  */
74 
75 /*
76  * Fault-tolerant iconv() function.
77  *
78  * This routine was borrowed from nail-11.25/mime.c and modified.  It
79  * tries to handle errno == EILSEQ by restarting at the next input
80  * byte (is this a good idea?).  All other errors are handled by the
81  * caller.
82  */
83 PUBLIC size_t
mime_iconv(iconv_t cd,const char ** inb,size_t * inbleft,char ** outb,size_t * outbleft)84 mime_iconv(iconv_t cd, const char **inb, size_t *inbleft, char **outb, size_t *outbleft)
85 {
86           size_t sz = 0;
87 
88           while ((sz = iconv(cd, __UNCONST(inb), inbleft, outb, outbleft))
89                        == (size_t)-1
90                               && errno == EILSEQ) {
91                     if (*outbleft > 0) {
92                               *(*outb)++ = '?';
93                               (*outbleft)--;
94                     } else {
95                               **outb = '\0';
96                               return E2BIG;
97                     }
98                     if (*inbleft > 0) {
99                               (*inb)++;
100                               (*inbleft)--;
101                     } else {
102                               **outb = '\0';
103                               break;
104                     }
105           }
106           return sz;
107 }
108 
109 /*
110  * This routine was mostly borrowed from src/usr.bin/iconv/iconv.c.
111  * We don't care about the invalid character count, so don't bother
112  * with __iconv().  We do care about robustness, so call iconv_ft()
113  * above to try to recover from errors.
114  */
115 #define INBUFSIZE 1024
116 #define OUTBUFSIZE (INBUFSIZE * 2)
117 
118 PUBLIC void
mime_ficonv(FILE * fi,FILE * fo,void * cookie)119 mime_ficonv(FILE *fi, FILE *fo, void *cookie)
120 {
121           char inbuf[INBUFSIZE], outbuf[OUTBUFSIZE], *out;
122           const char *in;
123           size_t inbytes, outbytes, ret;
124           iconv_t cd;
125 
126           /*
127            * NOTE: iconv_t is actually a pointer typedef, so this
128            * conversion is not what it appears to be!
129            */
130           cd = (iconv_t)cookie;
131 
132           while ((inbytes = fread(inbuf, 1, INBUFSIZE, fi)) > 0) {
133                     in = inbuf;
134                     while (inbytes > 0) {
135                               out = outbuf;
136                               outbytes = OUTBUFSIZE;
137                               ret = mime_iconv(cd, &in, &inbytes, &out, &outbytes);
138                               if (ret == (size_t)-1 && errno != E2BIG) {
139                                         if (errno != EINVAL || in == inbuf) {
140                                                   /* XXX - what is proper here?
141                                                    * Just copy out the remains? */
142                                                   (void)fprintf(fo,
143                                                       "\n\t[ iconv truncated message: %s ]\n\n",
144                                                       strerror(errno));
145                                                   return;
146                                         }
147                                         /*
148                                          * If here: errno == EINVAL && in != inbuf
149                                          */
150                                         /* incomplete input character */
151                                         (void)memmove(inbuf, in, inbytes);
152                                         ret = fread(inbuf + inbytes, 1,
153                                             INBUFSIZE - inbytes, fi);
154                                         if (ret == 0) {
155                                                   if (feof(fi)) {
156                                                             (void)fprintf(fo,
157                                                                 "\n\t[ unexpected end of file; "
158                                                                 "the last character is "
159                                                                 "incomplete. ]\n\n");
160                                                             return;
161                                                   }
162                                                   (void)fprintf(fo,
163                                                       "\n\t[ fread(): %s ]\n\n",
164                                                       strerror(errno));
165                                                   return;
166                                         }
167                                         in = inbuf;
168                                         inbytes += ret;
169 
170                               }
171                               if (outbytes < OUTBUFSIZE)
172                                         (void)fwrite(outbuf, 1, OUTBUFSIZE - outbytes, fo);
173                     }
174           }
175           /* reset the shift state of the output buffer */
176           outbytes = OUTBUFSIZE;
177           out = outbuf;
178           ret = iconv(cd, NULL, NULL, &out, &outbytes);
179           if (ret == (size_t)-1) {
180                     (void)fprintf(fo, "\n\t[ iconv(): %s ]\n\n",
181                         strerror(errno));
182                     return;
183           }
184           if (outbytes < OUTBUFSIZE)
185                     (void)fwrite(outbuf, 1, OUTBUFSIZE - outbytes, fo);
186 }
187 
188 #endif    /* CHARSET_SUPPORT */
189 
190 
191 
192 /************************************************************************
193  * Core base64 routines
194  *
195  * Defined in sec 6.8 of RFC 2045.
196  */
197 
198 /*
199  * Decode a base64 buffer.
200  *
201  *   bin:  buffer to hold the decoded (binary) result (see note 1).
202  *   b64:  buffer holding the encoded (base64) source.
203  *   cnt:  number of bytes in the b64 buffer to decode (see note 2).
204  *
205  * Return: the number of bytes written to the 'bin' buffer or -1 on
206  *         error.
207  * NOTES:
208  *   1) It is the callers responsibility to ensure that bin is large
209  *      enough to hold the result.
210  *   2) The b64 buffer should always contain a multiple of 4 bytes of
211  *      data!
212  */
213 PUBLIC ssize_t
mime_b64tobin(char * bin,const char * b64,size_t cnt)214 mime_b64tobin(char *bin, const char *b64, size_t cnt)
215 {
216           static const signed char b64index[] = {
217                     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
218                     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
219                     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
220                     52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-2,-1,-1,
221                     -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
222                     15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
223                     -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
224                     41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
225           };
226           unsigned char *p;
227           const unsigned char *q, *end;
228 
229 #define EQU         (unsigned)-2
230 #define BAD         (unsigned)-1
231 #define uchar64(c)  ((c) >= sizeof(b64index) ? BAD : (unsigned)b64index[(c)])
232 
233           p = (unsigned char *)bin;
234           q = (const unsigned char *)b64;
235           for (end = q + cnt; q < end; q += 4) {
236                     unsigned a = uchar64(q[0]);
237                     unsigned b = uchar64(q[1]);
238                     unsigned c = uchar64(q[2]);
239                     unsigned d = uchar64(q[3]);
240 
241                     if (a == BAD || a == EQU || b == BAD || b == EQU ||
242                         c == BAD || d == BAD)
243                               return -1;
244 
245                     *p++ = ((a << 2) | ((b & 0x30) >> 4));
246                     if (c == EQU)       { /* got '=' */
247                               if (d != EQU)
248                                         return -1;
249                               break;
250                     }
251                     *p++ = (((b & 0x0f) << 4) | ((c & 0x3c) >> 2));
252                     if (d == EQU) { /* got '=' */
253                               break;
254                     }
255                     *p++ = (((c & 0x03) << 6) | d);
256           }
257 
258 #undef uchar64
259 #undef EQU
260 #undef BAD
261 
262           return p - (unsigned char*)bin;
263 }
264 
265 /*
266  * Encode a buffer as a base64 result.
267  *
268  *   b64:  buffer to hold the encoded (base64) result (see note).
269  *   bin:  buffer holding the binary source.
270  *   cnt:  number of bytes in the bin buffer to encode.
271  *
272  * NOTE: it is the callers responsibility to ensure that 'b64' is
273  *       large enough to hold the result.
274  */
275 PUBLIC void
mime_bintob64(char * b64,const char * bin,size_t cnt)276 mime_bintob64(char *b64, const char *bin, size_t cnt)
277 {
278           static const char b64table[] =
279               "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
280           const unsigned char *p = (const unsigned char*)bin;
281           ssize_t i;
282 
283           for (i = cnt; i > 0; i -= 3) {
284                     unsigned a = p[0];
285                     unsigned b = p[1];
286                     unsigned c = p[2];
287 
288                     b64[0] = b64table[a >> 2];
289                     switch(i) {
290                     case 1:
291                               b64[1] = b64table[((a & 0x3) << 4)];
292                               b64[2] = '=';
293                               b64[3] = '=';
294                               break;
295                     case 2:
296                               b64[1] = b64table[((a & 0x3) << 4) | ((b & 0xf0) >> 4)];
297                               b64[2] = b64table[((b & 0xf) << 2)];
298                               b64[3] = '=';
299                               break;
300                     default:
301                               b64[1] = b64table[((a & 0x3) << 4) | ((b & 0xf0) >> 4)];
302                               b64[2] = b64table[((b & 0xf) << 2) | ((c & 0xc0) >> 6)];
303                               b64[3] = b64table[c & 0x3f];
304                               break;
305                     }
306                     p   += 3;
307                     b64 += 4;
308           }
309 }
310 
311 
312 #define MIME_BASE64_LINE_MAX  (4 * 19)  /* max line length is 76: see RFC2045 sec 6.8 */
313 
314 static void
mime_fB64_encode(FILE * fi,FILE * fo,void * cookie __unused)315 mime_fB64_encode(FILE *fi, FILE *fo, void *cookie __unused)
316 {
317           static char b64[MIME_BASE64_LINE_MAX];
318           static char mem[3 * (MIME_BASE64_LINE_MAX / 4)];
319           size_t cnt;
320           char *cp;
321           size_t limit;
322 #ifdef __lint__
323           cookie = cookie;
324 #endif
325           limit = 0;
326           if ((cp = value(ENAME_MIME_B64_LINE_MAX)) != NULL)
327                     limit = (size_t)atoi(cp);
328           if (limit == 0 || limit > sizeof(b64))
329                     limit = sizeof(b64);
330 
331           limit = 3 * roundup(limit, 4) / 4;
332           if (limit < 3)
333                     limit = 3;
334 
335           while ((cnt = fread(mem, sizeof(*mem), limit, fi)) > 0) {
336                     mime_bintob64(b64, mem, (size_t)cnt);
337                     (void)fwrite(b64, sizeof(*b64), (size_t)4 * roundup(cnt, 3) / 3, fo);
338                     (void)putc('\n', fo);
339           }
340 }
341 
342 static void
mime_fB64_decode(FILE * fi,FILE * fo,void * add_lf)343 mime_fB64_decode(FILE *fi, FILE *fo, void *add_lf)
344 {
345           char *line;
346           size_t len;
347           char *buf;
348           size_t buflen;
349 
350           buflen = 3 * (MIME_BASE64_LINE_MAX / 4);
351           buf = emalloc(buflen);
352 
353           while ((line = fgetln(fi, &len)) != NULL) {
354                     ssize_t binlen;
355                     if (line[len-1] == '\n') /* forget the trailing newline */
356                               len--;
357 
358                     /* trash trailing white space */
359                     for (/*EMPTY*/; len > 0 && is_WSP(line[len-1]); len--)
360                               continue;
361 
362                     /* skip leading white space */
363                     for (/*EMPTY*/; len > 0 && is_WSP(line[0]); len--, line++)
364                               continue;
365 
366                     if (len == 0)
367                               break;
368 
369                     if (3 * len > 4 * buflen) {
370                               buflen *= 2;
371                               buf = erealloc(buf, buflen);
372                     }
373 
374                     binlen = mime_b64tobin(buf, line, len);
375 
376                     if (binlen <= 0) {
377                               (void)fprintf(fo, "WARN: invalid base64 encoding\n");
378                               break;
379                     }
380                     (void)fwrite(buf, 1, (size_t)binlen, fo);
381           }
382 
383           free(buf);
384 
385           if (add_lf)
386                     (void)fputc('\n', fo);
387 }
388 
389 
390 /************************************************************************
391  * Core quoted-printable routines.
392  *
393  * Defined in sec 6.7 of RFC 2045.
394  */
395 
396 /*
397  * strtol(3), but inline and with easy error indication.
398  */
399 static inline int
_qp_cfromhex(char const * hex)400 _qp_cfromhex(char const *hex)
401 {
402           /* Be robust, allow lowercase hexadecimal letters, too */
403           static unsigned char const atoi16[] = {
404                     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 0x30-0x37 */
405                     0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x38-0x3F */
406                     0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, /* 0x40-0x47 */
407                     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x48-0x4f */
408                     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x50-0x57 */
409                     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x58-0x5f */
410                     0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF  /* 0x60-0x67 */
411           };
412           unsigned char i1, i2;
413           int r;
414 
415           if ((i1 = (unsigned char)hex[0] - '0') >= __arraycount(atoi16) ||
416               (i2 = (unsigned char)hex[1] - '0') >= __arraycount(atoi16))
417                     goto jerr;
418           i1 = atoi16[i1];
419           i2 = atoi16[i2];
420           if ((i1 | i2) & 0xF0)
421                     goto jerr;
422           r = i1;
423           r <<= 4;
424           r += i2;
425 jleave:
426           return r;
427 jerr:
428           r = -1;
429           goto jleave;
430 }
431 
432 /*
433  * Header specific "quoted-printable" decode!
434  * Differences with body QP decoding (see rfc 2047, sec 4.2):
435  * 1) '=' occurs _only_ when followed by two hex digits (FWS is not allowed).
436  * 2) Spaces can be encoded as '_' in headers for readability.
437  */
438 static ssize_t
mime_QPh_decode(char * outbuf,size_t outlen,const char * inbuf,size_t inlen)439 mime_QPh_decode(char *outbuf, size_t outlen, const char *inbuf, size_t inlen)
440 {
441           const char *p, *inend;
442           char *outend;
443           char *q;
444 
445           outend = outbuf + outlen;
446           inend = inbuf + inlen;
447           q = outbuf;
448           for (p = inbuf; p < inend; p++) {
449                     if (q >= outend)
450                               return -1;
451                     if (*p == '=') {
452                               p++;
453                               if (p + 1 < inend) {
454                                         int c = _qp_cfromhex(p++);
455                                         if (c < 0)
456                                                   return -1;
457                                         *q++ = (char)c;
458                               }
459                               else
460                                         return -1;
461                     }
462                     else if (*p == '_')  /* header's may encode ' ' as '_' */
463                               *q++ = ' ';
464                     else
465                               *q++ = *p;
466           }
467           return q - outbuf;
468 }
469 
470 
471 static int
mustquote(unsigned char * p,unsigned char * end,size_t l)472 mustquote(unsigned char *p, unsigned char *end, size_t l)
473 {
474 #define N 0         /* do not quote */
475 #define Q 1         /* must quote */
476 #define SP          2         /* white space */
477 #define XF          3         /* special character 'F' - maybe quoted */
478 #define XD          4         /* special character '.' - maybe quoted */
479 #define EQ          Q         /* '=' must be quoted */
480 #define TB          SP        /* treat '\t' as a space */
481 #define NL          N         /* don't quote '\n' (NL) - XXX - quoting here breaks the line length algorithm */
482 #define CR          Q         /* always quote a '\r' (CR) - it occurs only in a CRLF combo */
483 
484           static const signed char quotetab[] = {
485                      Q, Q, Q, Q,  Q, Q, Q, Q,  Q,TB,NL, Q,  Q,CR, Q, Q,
486                      Q, Q, Q, Q,  Q, Q, Q, Q,  Q, Q, Q, Q,  Q, Q, Q, Q,
487                     SP, N, N, N,  N, N, N, N,  N, N, N, N,  N, N,XD, N,
488                      N, N, N, N,  N, N, N, N,  N, N, N, N,  N,EQ, N, N,
489 
490                      N, N, N, N,  N, N,XF, N,  N, N, N, N,  N, N, N, N,
491                      N, N, N, N,  N, N, N, N,  N, N, N, N,  N, N, N, N,
492                      N, N, N, N,  N, N, N, N,  N, N, N, N,  N, N, N, N,
493                      N, N, N, N,  N, N, N, N,  N, N, N, N,  N, N, N, Q,
494           };
495           int flag = *p > 0x7f ? Q : quotetab[*p];
496 
497           if (flag == N)
498                     return 0;
499           if (flag == Q)
500                     return 1;
501           if (flag == SP)
502                     return p + 1 < end && p[1] == '\n'; /* trailing white space */
503 
504           /* The remainder are special start-of-line cases. */
505           if (l != 0)
506                     return 0;
507 
508           if (flag == XF)     /* line may start with "From" */
509                     return p + 4 < end && p[1] == 'r' && p[2] == 'o' && p[3] == 'm';
510 
511           if (flag == XD)     /* line may consist of a single dot */
512                     return p + 1 < end && p[1] == '\n';
513 
514           errx(EXIT_FAILURE,
515               "mustquote: invalid logic: *p=0x%x (%d) flag=%d, l=%zu\n",
516               *p, *p, flag, l);
517           /* NOT REACHED */
518           return 0; /* appease GCC */
519 
520 #undef N
521 #undef Q
522 #undef SP
523 #undef XX
524 #undef EQ
525 #undef TB
526 #undef NL
527 #undef CR
528 }
529 
530 
531 #define MIME_QUOTED_LINE_MAX  76  /* QP max length: see RFC2045 sec 6.7 */
532 
533 static void
fput_quoted_line(FILE * fo,char * line,size_t len,size_t limit)534 fput_quoted_line(FILE *fo, char *line, size_t len, size_t limit)
535 {
536           size_t l; /* length of current output line */
537           unsigned char *beg;
538           unsigned char *end;
539           unsigned char *p;
540 
541           assert(limit <= MIME_QUOTED_LINE_MAX);
542 
543           beg = (unsigned char*)line;
544           end = beg + len;
545           l = 0;
546           for (p = (unsigned char*)line; p < end; p++) {
547                     if (mustquote(p, end, l)) {
548                               if (l + 4 > limit) {
549                                         (void)fputs("=\n", fo);
550                                         l = 0;
551                               }
552                               (void)fprintf(fo, "=%02X", *p);
553                               l += 3;
554                     }
555                     else {
556                               if (*p == '\n') {
557                                         if (p > beg && p[-1] == '\r') {
558                                                   if (l + 4 > limit)
559                                                             (void)fputs("=\n", fo);
560                                                   (void)fputs("=0A=", fo);
561                                         }
562                                         l = (size_t)-1;
563                               }
564                               else if (l + 2 > limit) {
565                                         (void)fputs("=\n", fo);
566                                         l = 0;
567                               }
568                               (void)putc(*p, fo);
569                               l++;
570                     }
571           }
572           /*
573            * Lines ending in a blank must escape the newline.
574            */
575           if (len && is_WSP(p[-1]))
576                     (void)fputs("=\n", fo);
577 }
578 
579 static void
mime_fQP_encode(FILE * fi,FILE * fo,void * cookie __unused)580 mime_fQP_encode(FILE *fi, FILE *fo, void *cookie __unused)
581 {
582           char *line;
583           size_t len;
584           char *cp;
585           size_t limit;
586 
587 #ifdef __lint__
588           cookie = cookie;
589 #endif
590           limit = 0;
591           if ((cp = value(ENAME_MIME_QP_LINE_MAX)) != NULL)
592                     limit = (size_t)atoi(cp);
593           if (limit == 0 || limit > MIME_QUOTED_LINE_MAX)
594                     limit = MIME_QUOTED_LINE_MAX;
595           if (limit < 4)
596                     limit = 4;
597 
598           while ((line = fgetln(fi, &len)) != NULL)
599                     fput_quoted_line(fo, line, len, limit);
600 }
601 
602 static void
mime_fQP_decode(FILE * fi,FILE * fo,void * cookie __unused)603 mime_fQP_decode(FILE *fi, FILE *fo, void *cookie __unused)
604 {
605           char *line;
606           size_t len;
607 
608 #ifdef __lint__
609           cookie = cookie;
610 #endif
611           while ((line = fgetln(fi, &len)) != NULL) {
612                     char *p;
613                     char *end;
614 
615                     end = line + len;
616                     for (p = line; p < end; p++) {
617                               if (*p == '=') {
618                                         p++;
619                                         while (p < end && is_WSP(*p))
620                                                   p++;
621                                         if (*p != '\n' && p + 1 < end) {
622                                                   int c = _qp_cfromhex(p++);
623                                                   if (c >= 0)
624                                                             (void)fputc(c, fo);
625                                                   else
626                                                             (void)fputs("[?]", fo);
627                                         }
628                               }
629                               else
630                                         (void)fputc(*p, fo);
631                     }
632           }
633 }
634 
635 
636 /************************************************************************
637  * Routines to select the codec by name.
638  */
639 
640 PUBLIC void
mime_fio_copy(FILE * fi,FILE * fo,void * cookie __unused)641 mime_fio_copy(FILE *fi, FILE *fo, void *cookie __unused)
642 {
643           int c;
644 
645 #ifdef __lint__
646           cookie = cookie;
647 #endif
648           while ((c = getc(fi)) != EOF)
649                     (void)putc(c, fo);
650 
651           (void)fflush(fo);
652           if (ferror(fi)) {
653                     warn("read");
654                     rewind(fi);
655                     return;
656           }
657           if (ferror(fo)) {
658                     warn("write");
659                     (void)Fclose(fo);
660                     rewind(fi);
661                     return;
662           }
663 }
664 
665 
666 static const struct transfer_encoding_s {
667           const char          *name;
668           mime_codec_t        enc;
669           mime_codec_t        dec;
670 } transfer_encoding_tbl[] = {
671           { MIME_TRANSFER_7BIT,         mime_fio_copy,          mime_fio_copy },
672           { MIME_TRANSFER_8BIT,         mime_fio_copy,          mime_fio_copy },
673           { MIME_TRANSFER_BINARY,       mime_fio_copy,          mime_fio_copy },
674           { MIME_TRANSFER_QUOTED, mime_fQP_encode,    mime_fQP_decode },
675           { MIME_TRANSFER_BASE64, mime_fB64_encode,   mime_fB64_decode },
676           { NULL,                       NULL,                   NULL },
677 };
678 
679 
680 PUBLIC mime_codec_t
mime_fio_encoder(const char * ename)681 mime_fio_encoder(const char *ename)
682 {
683           const struct transfer_encoding_s *tep = NULL;
684 
685           if (ename == NULL)
686                     return NULL;
687 
688           for (tep = transfer_encoding_tbl; tep->name; tep++)
689                     if (strcasecmp(tep->name, ename) == 0)
690                               break;
691           return tep->enc;
692 }
693 
694 PUBLIC mime_codec_t
mime_fio_decoder(const char * ename)695 mime_fio_decoder(const char *ename)
696 {
697           const struct transfer_encoding_s *tep = NULL;
698 
699           if (ename == NULL)
700                     return NULL;
701 
702           for (tep = transfer_encoding_tbl; tep->name; tep++)
703                     if (strcasecmp(tep->name, ename) == 0)
704                               break;
705           return tep->dec;
706 }
707 
708 /*
709  * Decode a RFC 2047 extended message header *encoded-word*.
710  * *encoding* is the corresponding character of the *encoded-word*.
711  */
712 PUBLIC ssize_t
mime_rfc2047_decode(char encoding,char * outbuf,size_t outlen,const char * inbuf,size_t inlen)713 mime_rfc2047_decode(char encoding, char *outbuf, size_t outlen,
714           const char *inbuf, size_t inlen)
715 {
716           ssize_t declen = -1;
717 
718           if (encoding == 'B' || encoding == 'b') {
719                     if (outlen >= 3 * roundup(inlen, 4) / 4)
720                               declen = mime_b64tobin(outbuf, inbuf, inlen);
721           } else if (encoding == 'Q' || encoding == 'q')
722                     declen = mime_QPh_decode(outbuf, outlen, inbuf, inlen);
723           return declen;
724 }
725 
726 /*
727  * This is for use in complete.c and mime.c to get the list of
728  * encoding names without exposing the transfer_encoding_tbl[].  The
729  * first name is returned if called with a pointer to a NULL pointer.
730  * Subsequent calls with the same cookie give successive names.  A
731  * NULL return indicates the end of the list.
732  */
733 PUBLIC const char *
mime_next_encoding_name(const void ** cookie)734 mime_next_encoding_name(const void **cookie)
735 {
736           const struct transfer_encoding_s *tep;
737 
738           tep = *cookie;
739           if (tep == NULL)
740                     tep = transfer_encoding_tbl;
741 
742           *cookie = tep->name ? &tep[1] : NULL;
743 
744           return tep->name;
745 }
746 
747 #endif /* MIME_SUPPORT */
748