mk-conf.sh: adjust fixed CONFIG=urations for mandatory options
[s-mailx.git] / mime_cte.c
blob7e72b972aaf7747ca04067c509eeeae190367a0a
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Content-Transfer-Encodings as defined in RFC 2045:
3 *@ - Quoted-Printable, section 6.7
4 *@ - Base64, section 6.8
6 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
7 * Copyright (c) 2012 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
8 */
9 /* QP quoting idea, _b64_decode(), b64_encode() taken from NetBSDs mailx(1): */
10 /* $NetBSD: mime_codecs.c,v 1.9 2009/04/10 13:08:25 christos Exp $ */
12 * Copyright (c) 2006 The NetBSD Foundation, Inc.
13 * All rights reserved.
15 * This code is derived from software contributed to The NetBSD Foundation
16 * by Anon Ymous.
18 * Redistribution and use in source and binary forms, with or without
19 * modification, are permitted provided that the following conditions
20 * are met:
21 * 1. Redistributions of source code must retain the above copyright
22 * notice, this list of conditions and the following disclaimer.
23 * 2. Redistributions in binary form must reproduce the above copyright
24 * notice, this list of conditions and the following disclaimer in the
25 * documentation and/or other materials provided with the distribution.
27 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 enum _qact {
45 N = 0, /* Do not quote */
46 Q = 1, /* Must quote */
47 SP = 2, /* sp */
48 XF = 3, /* Special character 'F' - maybe quoted */
49 XD = 4, /* Special character '.' - maybe quoted */
50 US = '_', /* In header, special character ' ' quoted as '_' */
51 QM = '?', /* In header, special character ? not always quoted */
52 EQ = Q, /* '=' must be quoted */
53 TB = SP, /* Treat '\t' as a space */
54 NL = N, /* Don't quote '\n' (NL) */
55 CR = Q /* Always quote a '\r' (CR) */
58 /* Lookup tables to decide wether a character must be encoded or not.
59 * Email header differences according to RFC 2047, section 4.2:
60 * - also quote SP (as the underscore _), TAB, ?, _, CR, LF
61 * - don't care about the special ^F[rom] and ^.$ */
62 static ui8_t const _qtab_body[] = {
63 Q, Q, Q, Q, Q, Q, Q, Q, Q,TB,NL, Q, Q,CR, Q, Q,
64 Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q,
65 SP, N, N, N, N, N, N, N, N, N, N, N, N, N,XD, N,
66 N, N, N, N, N, N, N, N, N, N, N, N, N,EQ, N, N,
68 N, N, N, N, N, N,XF, N, N, N, N, N, N, N, N, N,
69 N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N,
70 N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N,
71 N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, Q,
73 _qtab_head[] = {
74 Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q,
75 Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q,
76 US, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N,
77 N, N, N, N, N, N, N, N, N, N, N, N, N,EQ, N,QM,
79 N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N,
80 N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, Q,
81 N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N,
82 N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, Q,
85 /* Check wether **s* must be quoted according to *ishead*, else body rules;
86 * *sol* indicates wether we are at the first character of a line/field */
87 SINLINE enum _qact _mustquote(char const *s, char const *e, bool_t sol,
88 bool_t ishead);
90 /* Convert c to/from a hexadecimal character string */
91 SINLINE char * _qp_ctohex(char *store, char c);
92 SINLINE si32_t _qp_cfromhex(char const *hex);
94 /* Trim WS and make *work* point to the decodable range of *in*.
95 * Return the amount of bytes a b64_decode operation on that buffer requires */
96 static size_t _b64_decode_prepare(struct str *work,
97 struct str const *in);
99 /* Perform b64_decode on sufficiently spaced & multiple-of-4 base *in*put.
100 * Return number of useful bytes in *out* or -1 on error */
101 static ssize_t _b64_decode(struct str *out, struct str *in);
103 SINLINE enum _qact
104 _mustquote(char const *s, char const *e, bool_t sol, bool_t ishead)
106 ui8_t const *qtab;
107 enum _qact a, r;
108 NYD2_ENTER;
110 qtab = ishead ? _qtab_head : _qtab_body;
111 a = ((ui8_t)*s > 0x7F) ? Q : qtab[(ui8_t)*s];
113 if ((r = a) == N || (r = a) == Q)
114 goto jleave;
115 r = Q;
117 /* Special header fields */
118 if (ishead) {
119 /* ' ' -> '_' */
120 if (a == US) {
121 r = US;
122 goto jleave;
124 /* Treat '?' only special if part of '=?' .. '?=' (still too much quoting
125 * since it's '=?CHARSET?CTE?stuff?=', and especially the trailing ?=
126 * should be hard too match */
127 if (a == QM && ((!sol && s[-1] == '=') || (s < e && s[1] == '=')))
128 goto jleave;
129 goto jnquote;
132 /* Body-only */
134 if (a == SP) {
135 /* WS only if trailing white space */
136 if (PTRCMP(s + 1, ==, e) || s[1] == '\n')
137 goto jleave;
138 goto jnquote;
141 /* Rest are special begin-of-line cases */
142 if (!sol)
143 goto jnquote;
145 /* ^From */
146 if (a == XF) {
147 if (PTRCMP(s + 4, <, e) && s[1] == 'r' && s[2] == 'o' && s[3] == 'm')
148 goto jleave;
149 goto jnquote;
151 /* ^.$ */
152 if (a == XD && (PTRCMP(s + 1, ==, e) || s[1] == '\n'))
153 goto jleave;
154 jnquote:
155 r = N;
156 jleave:
157 NYD2_LEAVE;
158 return r;
161 SINLINE char *
162 _qp_ctohex(char *store, char c)
164 static char const hexmap[] = "0123456789ABCDEF";
165 NYD2_ENTER;
167 store[2] = '\0';
168 store[1] = hexmap[(ui8_t)c & 0x0F];
169 c = ((ui8_t)c >> 4) & 0x0F;
170 store[0] = hexmap[(ui8_t)c];
171 NYD2_LEAVE;
172 return store;
175 SINLINE si32_t
176 _qp_cfromhex(char const *hex)
178 /* Be robust, allow lowercase hexadecimal letters, too */
179 static ui8_t const atoi16[] = {
180 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 0x30-0x37 */
181 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x38-0x3F */
182 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, /* 0x40-0x47 */
183 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x48-0x4f */
184 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x50-0x57 */
185 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x58-0x5f */
186 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF /* 0x60-0x67 */
189 ui8_t i1, i2;
190 si32_t r;
191 NYD2_ENTER;
193 if ((i1 = (ui8_t)hex[0] - '0') >= NELEM(atoi16) ||
194 (i2 = (ui8_t)hex[1] - '0') >= NELEM(atoi16))
195 goto jerr;
196 i1 = atoi16[i1];
197 i2 = atoi16[i2];
198 if ((i1 | i2) & 0xF0u)
199 goto jerr;
200 r = i1;
201 r <<= 4;
202 r += i2;
203 jleave:
204 NYD2_LEAVE;
205 return r;
206 jerr:
207 r = -1;
208 goto jleave;
211 static size_t
212 _b64_decode_prepare(struct str *work, struct str const *in)
214 char *cp;
215 size_t cp_len;
216 NYD2_ENTER;
218 cp = in->s;
219 cp_len = in->l;
221 while (cp_len > 0 && spacechar(*cp))
222 ++cp, --cp_len;
223 work->s = cp;
225 for (cp += cp_len; cp_len > 0; --cp_len) {
226 char c = *--cp;
227 if (!spacechar(c))
228 break;
230 work->l = cp_len;
232 if (cp_len > 16)
233 cp_len = ((cp_len * 3) >> 2) + (cp_len >> 3);
234 NYD2_LEAVE;
235 return cp_len;
238 static ssize_t
239 _b64_decode(struct str *out, struct str *in)
241 static signed char const b64index[] = {
242 -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
243 -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
244 -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
245 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-2,-1,-1,
246 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
247 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
248 -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
249 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
251 #define EQU (ui32_t)-2
252 #define BAD (ui32_t)-1
253 #define uchar64(c) ((c) >= sizeof(b64index) ? BAD : (ui32_t)b64index[(c)])
255 ssize_t ret = -1;
256 ui8_t *p;
257 ui8_t const *q, *end;
258 NYD2_ENTER;
260 p = (ui8_t*)out->s;
261 q = (ui8_t const*)in->s;
262 out->l = 0;
264 for (end = q + in->l; PTRCMP(q + 4, <=, end); q += 4) {
265 ui32_t a = uchar64(q[0]), b = uchar64(q[1]), c = uchar64(q[2]),
266 d = uchar64(q[3]);
268 if (a >= EQU || b >= EQU || c == BAD || d == BAD)
269 goto jleave;
271 *p++ = ((a << 2) | ((b & 0x30) >> 4));
272 if (c == EQU) { /* got '=' */
273 if (d != EQU)
274 goto jleave;
275 break;
277 *p++ = (((b & 0x0F) << 4) | ((c & 0x3C) >> 2));
278 if (d == EQU) /* got '=' */
279 break;
280 *p++ = (((c & 0x03) << 6) | d);
282 #undef uchar64
283 #undef EQU
284 #undef BAD
286 ret = PTR2SIZE((char*)p - out->s);
287 out->l = (size_t)ret;
288 jleave:
289 in->l -= PTR2SIZE((char*)UNCONST(q) - in->s);
290 in->s = UNCONST(q);
291 NYD2_LEAVE;
292 return ret;
295 FL char *
296 mime_char_to_hexseq(char store[3], char c)
298 char *rv;
299 NYD2_ENTER;
301 rv = _qp_ctohex(store, c);
302 NYD2_LEAVE;
303 return rv;
306 FL si32_t
307 mime_hexseq_to_char(char const *hex)
309 si32_t rv;
310 NYD2_ENTER;
312 rv = _qp_cfromhex(hex);
313 NYD2_LEAVE;
314 return rv;
317 FL size_t
318 mime_cte_mustquote(char const *ln, size_t lnlen, bool_t ishead)
320 size_t ret;
321 bool_t sol;
322 NYD_ENTER;
324 for (ret = 0, sol = TRU1; lnlen > 0; sol = FAL0, ++ln, --lnlen)
325 ret += (_mustquote(ln, ln + lnlen, sol, ishead) != N);
326 NYD_LEAVE;
327 return ret;
330 FL size_t
331 qp_encode_calc_size(size_t len)
333 size_t bytes, lines;
334 NYD_ENTER;
336 /* The worst case sequence is 'CRLF' -> '=0D=0A=\n\0'.
337 * However, we must be aware that (a) the output may span multiple lines
338 * and (b) the input does not end with a newline itself (nonetheless):
339 * LC_ALL=C awk 'BEGIN{
340 * for(i = 0; i < 100000; ++i) printf "\xC3\xBC"
341 * }' |
342 * MAILRC=/dev/null LC_ALL=en_US.UTF-8 s-nail -nvvd \
343 * -Ssendcharsets=utf8 -s testsub ./LETTER */
344 bytes = len * 3;
345 lines = bytes / QP_LINESIZE;
346 len += lines;
348 bytes = len * 3;
349 /* Trailing hard NL may be missing, so there may be two lines.
350 * Thus add soft + hard NL per line and a trailing NUL */
351 lines = (bytes / QP_LINESIZE) + 1;
352 lines <<= 1;
353 bytes += lines;
354 len = ++bytes;
356 NYD_LEAVE;
357 return len;
360 #ifdef notyet
361 FL struct str *
362 qp_encode_cp(struct str *out, char const *cp, enum qpflags flags)
364 struct str in;
365 NYD_ENTER;
367 in.s = UNCONST(cp);
368 in.l = strlen(cp);
369 out = qp_encode(out, &in, flags);
370 NYD_LEAVE;
371 return out;
374 FL struct str *
375 qp_encode_buf(struct str *out, void const *vp, size_t vp_len,
376 enum qpflags flags)
378 struct str in;
379 NYD_ENTER;
381 in.s = UNCONST(vp);
382 in.l = vp_len;
383 out = qp_encode(out, &in, flags);
384 NYD_LEAVE;
385 return out;
387 #endif /* notyet */
389 FL struct str *
390 qp_encode(struct str *out, struct str const *in, enum qpflags flags)
392 bool_t sol = (flags & QP_ISHEAD ? FAL0 : TRU1), seenx;
393 ssize_t lnlen;
394 char *qp;
395 char const *is, *ie;
396 NYD_ENTER;
398 if (!(flags & QP_BUF)) {
399 lnlen = qp_encode_calc_size(in->l);
400 out->s = (flags & QP_SALLOC) ? salloc(lnlen) : srealloc(out->s, lnlen);
402 qp = out->s;
403 is = in->s;
404 ie = is + in->l;
406 /* QP_ISHEAD? */
407 if (!sol) {
408 for (seenx = FAL0, sol = TRU1; is < ie; sol = FAL0, ++qp) {
409 enum _qact mq = _mustquote(is, ie, sol, TRU1);
410 char c = *is++;
412 if (mq == N) {
413 /* We convert into a single *encoded-word*, that'll end up in
414 * =?C?Q??=; quote '?' from when we're inside there on */
415 if (seenx && c == '?')
416 goto jheadq;
417 *qp = c;
418 } else if (mq == US)
419 *qp = US;
420 else {
421 seenx = TRU1;
422 jheadq:
423 *qp++ = '=';
424 qp = _qp_ctohex(qp, c) + 1;
427 goto jleave;
430 /* The body needs to take care for soft line breaks etc. */
431 for (lnlen = 0, seenx = FAL0; is < ie; sol = FAL0) {
432 enum _qact mq = _mustquote(is, ie, sol, FAL0);
433 char c = *is++;
435 if (mq == N && (c != '\n' || !seenx)) {
436 *qp++ = c;
437 if (++lnlen < QP_LINESIZE - 1)
438 continue;
439 /* Don't write a soft line break when we're in the last possible
440 * column and either an LF has been written or only an LF follows, as
441 * that'll end the line anyway */
442 /* XXX but - ensure is+1>=ie, then??
443 * xxx and/or - what about resetting lnlen; that contra
444 * xxx dicts input==1 input line assertion, though */
445 if (c == '\n' || is == ie || *is == '\n')
446 continue;
447 jsoftnl:
448 qp[0] = '=';
449 qp[1] = '\n';
450 qp += 2;
451 lnlen = 0;
452 continue;
455 if (lnlen > QP_LINESIZE - 3 - 1) {
456 qp[0] = '=';
457 qp[1] = '\n';
458 qp += 2;
459 lnlen = 0;
461 *qp++ = '=';
462 qp = _qp_ctohex(qp, c);
463 qp += 2;
464 lnlen += 3;
465 if (c != '\n' || !seenx)
466 seenx = (c == '\r');
467 else {
468 seenx = FAL0;
469 goto jsoftnl;
473 /* Enforce soft line break if we haven't seen LF */
474 if (in->l > 0 && *--is != '\n') {
475 qp[0] = '=';
476 qp[1] = '\n';
477 qp += 2;
479 jleave:
480 out->l = PTR2SIZE(qp - out->s);
481 out->s[out->l] = '\0';
482 NYD_LEAVE;
483 return out;
486 FL int
487 qp_decode(struct str *out, struct str const *in, struct str *rest)
489 int ret = STOP;
490 char *os, *oc;
491 char const *is, *ie;
492 NYD_ENTER;
494 if (rest != NULL && rest->l != 0) {
495 os = out->s;
496 *out = *rest;
497 rest->s = os;
498 rest->l = 0;
501 oc = os =
502 out->s = srealloc(out->s, out->l + in->l + 3);
503 oc += out->l;
504 is = in->s;
505 ie = is + in->l;
507 /* Decoding encoded-word (RFC 2049) in a header field? */
508 if (rest == NULL) {
509 while (is < ie) {
510 si32_t c = *is++;
511 if (c == '=') {
512 if (PTRCMP(is + 1, >=, ie)) {
513 ++is;
514 goto jehead;
516 c = _qp_cfromhex(is);
517 is += 2;
518 if (c >= 0)
519 *oc++ = (char)c;
520 else {
521 /* Invalid according to RFC 2045, section 6.7. Almost follow */
522 jehead:
523 /* TODO 0xFFFD
524 *oc[0] = '['; oc[1] = '?'; oc[2] = ']';
525 *oc += 3; 0xFFFD TODO
526 */ *oc++ = '?';
528 } else
529 *oc++ = (c == '_') ? ' ' : (char)c;
531 goto jleave; /* XXX QP decode, header: errors not reported */
534 /* Decoding a complete message/mimepart body line */
535 while (is < ie) {
536 si32_t c = *is++;
537 if (c != '=') {
538 *oc++ = (char)c;
539 continue;
542 /* RFC 2045, 6.7:
543 * Therefore, when decoding a Quoted-Printable body, any
544 * trailing white space on a line must be deleted, as it will
545 * necessarily have been added by intermediate transport
546 * agents */
547 for (; is < ie && blankchar(*is); ++is)
549 if (PTRCMP(is + 1, >=, ie)) {
550 /* Soft line break? */
551 if (*is == '\n')
552 goto jsoftnl;
553 ++is;
554 goto jebody;
557 /* Not a soft line break? */
558 if (*is != '\n') {
559 c = _qp_cfromhex(is);
560 is += 2;
561 if (c >= 0)
562 *oc++ = (char)c;
563 else {
564 /* Invalid according to RFC 2045, section 6.7.
565 * Almost follow it and include the = and the follow char */
566 jebody:
567 /* TODO 0xFFFD
568 *oc[0] = '['; oc[1] = '?'; oc[2] = ']';
569 *oc += 3; 0xFFFD TODO
570 */ *oc++ = '?';
572 continue;
575 /* CRLF line endings are encoded as QP, followed by a soft line break, so
576 * check for this special case, and simply forget we have seen one, so as
577 * not to end up with the entire DOS file in a contiguous buffer */
578 jsoftnl:
579 if (oc > os && oc[-1] == '\n') {
580 #if 0 /* TODO qp_decode() we do not normalize CRLF
581 * TODO to LF because for that we would need
582 * TODO to know if we are about to write to
583 * TODO the display or do save the file!
584 * TODO 'hope the MIME/send layer rewrite will
585 * TODO offer the possibility to DTRT */
586 if (oc - 1 > os && oc[-2] == '\r') {
587 --oc;
588 oc[-1] = '\n';
590 #endif
591 break;
593 out->l = PTR2SIZE(oc - os);
594 rest->s = srealloc(rest->s, rest->l + out->l);
595 memcpy(rest->s + rest->l, out->s, out->l);
596 rest->l += out->l;
597 oc = os;
598 break;
600 /* XXX RFC: QP decode should check no trailing WS on line */
601 jleave:
602 out->l = PTR2SIZE(oc - os);
603 ret = OKAY;
604 NYD_LEAVE;
605 return ret;
608 FL size_t
609 b64_encode_calc_size(size_t len)
611 NYD_ENTER;
612 len = (len * 4) / 3;
613 len += (((len / B64_ENCODE_INPUT_PER_LINE) + 1) * 3);
614 len += 2 + 1; /* CRLF, \0 */
615 NYD_LEAVE;
616 return len;
619 FL struct str *
620 b64_encode(struct str *out, struct str const *in, enum b64flags flags)
622 static char const b64table[] =
623 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
625 ui8_t const *p;
626 ssize_t i, lnlen;
627 char *b64;
628 NYD_ENTER;
630 p = (ui8_t const*)in->s;
632 if (!(flags & B64_BUF)) {
633 i = b64_encode_calc_size(in->l);
634 out->s = (flags & B64_SALLOC) ? salloc(i) : srealloc(out->s, i);
636 b64 = out->s;
638 if (!(flags & (B64_CRLF | B64_LF)))
639 flags &= ~B64_MULTILINE;
641 for (lnlen = 0, i = (ssize_t)in->l; i > 0; p += 3, i -= 3) {
642 ui32_t a = p[0], b, c;
644 b64[0] = b64table[a >> 2];
645 switch (i) {
646 case 1:
647 b64[1] = b64table[((a & 0x3) << 4)];
648 b64[2] =
649 b64[3] = '=';
650 break;
651 case 2:
652 b = p[1];
653 b64[1] = b64table[((a & 0x03) << 4) | ((b & 0xF0u) >> 4)];
654 b64[2] = b64table[((b & 0x0F) << 2)];
655 b64[3] = '=';
656 break;
657 default:
658 b = p[1];
659 c = p[2];
660 b64[1] = b64table[((a & 0x03) << 4) | ((b & 0xF0u) >> 4)];
661 b64[2] = b64table[((b & 0x0F) << 2) | ((c & 0xC0u) >> 6)];
662 b64[3] = b64table[c & 0x3F];
663 break;
666 b64 += 4;
667 if (!(flags & B64_MULTILINE))
668 continue;
669 lnlen += 4;
670 if (lnlen < B64_LINESIZE)
671 continue;
673 lnlen = 0;
674 if (flags & B64_CRLF)
675 *b64++ = '\r';
676 if (flags & (B64_CRLF | B64_LF))
677 *b64++ = '\n';
680 if ((flags & (B64_CRLF | B64_LF)) &&
681 (!(flags & B64_MULTILINE) || lnlen != 0)) {
682 if (flags & B64_CRLF)
683 *b64++ = '\r';
684 if (flags & (B64_CRLF | B64_LF))
685 *b64++ = '\n';
687 out->l = PTR2SIZE(b64 - out->s);
688 out->s[out->l] = '\0';
689 NYD_LEAVE;
690 return out;
693 FL struct str *
694 b64_encode_buf(struct str *out, void const *vp, size_t vp_len,
695 enum b64flags flags)
697 struct str in;
698 NYD_ENTER;
700 in.s = UNCONST(vp);
701 in.l = vp_len;
702 out = b64_encode(out, &in, flags);
703 NYD_LEAVE;
704 return out;
707 #ifdef HAVE_SMTP
708 FL struct str *
709 b64_encode_cp(struct str *out, char const *cp, enum b64flags flags)
711 struct str in;
712 NYD_ENTER;
714 in.s = UNCONST(cp);
715 in.l = strlen(cp);
716 out = b64_encode(out, &in, flags);
717 NYD_LEAVE;
718 return out;
720 #endif
722 FL int
723 b64_decode(struct str *out, struct str const *in, struct str *rest)
725 struct str work;
726 char *x;
727 int ret = STOP;
728 size_t len;
729 NYD_ENTER;
731 len = _b64_decode_prepare(&work, in);
733 /* Ignore an empty input, as may happen for an empty final line */
734 if (work.l == 0) {
735 /* With B64_T there may be leftover decoded data for iconv(3), even if
736 * that means it's incomplete multibyte character we have to copy over */
737 /* XXX strictly speaking this should not be handled in here,
738 * XXX since its leftover decoded data from an iconv(3);
739 * XXX like this we shared the prototype with QP, though?? */
740 if (rest != NULL && rest->l > 0) {
741 x = out->s;
742 *out = *rest;
743 rest->s = x;
744 rest->l = 0;
745 } else
746 out->l = 0;
747 ret = OKAY;
748 goto jleave;
750 if (work.l >= 4 && !(work.l & 3)) {
751 out->s = srealloc(out->s, len);
752 ret = OKAY;
754 if (ret != OKAY || (ssize_t)(len = _b64_decode(out, &work)) < 0)
755 goto jerr;
756 jleave:
757 NYD_LEAVE;
758 return ret;
760 jerr: {
761 char const *err = _("[Invalid Base64 encoding ignored]\n");
762 len = strlen(err);
763 x = out->s = srealloc(out->s, len + 1 +1);
764 if (rest != NULL && rest->l)
765 *x++ = '\n';
766 memcpy(x, err, len);
767 x += len;
768 *x = '\0';
769 out->l = PTR2SIZE(x - out->s);
770 if (rest != NULL)
771 rest->l = 0;
772 ret = STOP;
773 goto jleave;
777 /* s-it-mode */