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