1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Content-Transfer-Encodings as defined in RFC 2045 (and RFC 2047):
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 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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
18 * Redistribution and use in source and binary forms, with or without
19 * modification, are permitted provided that the following conditions
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 #define n_FILE mime_enc
42 #ifndef HAVE_AMALGAMATION
47 N
= 0, /* Do not quote */
48 Q
= 1, /* Must quote */
50 XF
= 3, /* Special character 'F' - maybe quoted */
51 XD
= 4, /* Special character '.' - maybe quoted */
52 UU
= 5, /* In header, _ must be quoted in encoded word */
53 US
= '_', /* In header, ' ' must be quoted as _ in encoded word */
54 QM
= '?', /* In header, special character ? not always quoted */
55 EQ
= '=', /* In header, '=' must be quoted in encoded word */
56 HT
='\t', /* In body HT=SP, in head HT=HT, but quote in encoded word */
57 NL
= N
, /* Don't quote '\n' (NL) */
58 CR
= Q
/* Always quote a '\r' (CR) */
61 /* Lookup tables to decide wether a character must be encoded or not.
62 * Email header differences according to RFC 2047, section 4.2:
63 * - also quote SP (as the underscore _), TAB, ?, _, CR, LF
64 * - don't care about the special ^F[rom] and ^.$ */
65 static ui8_t
const _qtab_body
[] = {
66 Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
,SP
,NL
, Q
, Q
,CR
, Q
, Q
,
67 Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
,
68 SP
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
,XD
, N
,
69 N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, Q
, N
, N
,
71 N
, N
, N
, N
, N
, N
,XF
, N
, N
, N
, N
, N
, N
, N
, N
, N
,
72 N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
,
73 N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
,
74 N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, Q
,
77 Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
,HT
, Q
, Q
, Q
, Q
, Q
, Q
,
78 Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
, Q
,
79 US
, 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
,EQ
, N
,QM
,
82 N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
,
83 N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
,UU
,
84 N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
,
85 N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, N
, Q
,
88 /* For decoding be robust and allow lowercase letters, too */
89 static char const _qp_itoa16
[] = "0123456789ABCDEF";
90 static ui8_t
const _qp_atoi16
[] = {
91 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 0x30-0x37 */
92 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x38-0x3F */
93 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, /* 0x40-0x47 */
94 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x48-0x4f */
95 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x50-0x57 */
96 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x58-0x5f */
97 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF /* 0x60-0x67 */
100 /* The decoding table is only accessed via _B64_DECUI8() */
101 static char const _b64_enctbl
[] =
102 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
103 static signed char const _b64__dectbl
[] = {
104 -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
105 -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
106 -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
107 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-2,-1,-1,
108 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
109 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
110 -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
111 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
113 #define _B64_EQU (ui32_t)-2
114 #define _B64_BAD (ui32_t)-1
115 #define _B64_DECUI8(C) \
116 ((C) >= sizeof(_b64__dectbl) ? _B64_BAD : (ui32_t)_b64__dectbl[(ui8_t)(C)])
118 /* ASCII case-insensitive check wether Content-Transfer-Encoding: header body
119 * hbody defined this encoding type */
120 static bool_t
_is_ct_enc(char const *hbody
, char const *encoding
);
122 /* Check wether *s must be quoted according to flags, else body rules;
123 * sol indicates wether we are at the first character of a line/field */
124 SINLINE
enum _qact
_mustquote(char const *s
, char const *e
, bool_t sol
,
125 enum mime_enc_flags flags
);
127 /* Convert c to/from a hexadecimal character string */
128 SINLINE
char * _qp_ctohex(char *store
, char c
);
129 SINLINE si32_t
_qp_cfromhex(char const *hex
);
131 /* Trim WS and make work point to the decodable range of in*
132 * Return the amount of bytes a b64_decode operation on that buffer requires */
133 static size_t _b64_decode_prepare(struct str
*work
,
134 struct str
const *in
);
136 /* Perform b64_decode on sufficiently spaced & multiple-of-4 base in(put).
137 * Return number of useful bytes in out or -1 on error */
138 static ssize_t
_b64_decode(struct str
*out
, struct str
*in
);
141 _is_ct_enc(char const *hbody
, char const *encoding
)
148 quoted
= TRU1
, ++hbody
;
153 while (*hbody
!= '\0' && *encoding
!= '\0')
154 if ((c
= *hbody
++, lowerconv(c
) != *encoding
++))
158 if (quoted
&& *hbody
== '"')
160 if (*hbody
== '\0' || whitechar(*hbody
))
169 _mustquote(char const *s
, char const *e
, bool_t sol
, enum mime_enc_flags flags
)
175 qtab
= (flags
& (MIMEEF_ISHEAD
| MIMEEF_ISENCWORD
))
176 ? _qtab_head
: _qtab_body
;
177 a
= ((ui8_t
)*s
> 0x7F) ? Q
: qtab
[(ui8_t
)*s
];
179 if ((r
= a
) == N
|| (r
= a
) == Q
)
183 /* Special header fields */
184 if (flags
& (MIMEEF_ISHEAD
| MIMEEF_ISENCWORD
)) {
185 /* Special massage for encoded words */
186 if (flags
& MIMEEF_ISENCWORD
) {
200 /* Treat '?' only special if part of '=?' .. '?=' (still too much quoting
201 * since it's '=?CHARSET?CTE?stuff?=', and especially the trailing ?=
202 * should be hard too match */
203 if (a
== QM
&& ((!sol
&& s
[-1] == '=') || (s
< e
&& s
[1] == '=')))
211 /* WS only if trailing white space */
212 if (PTRCMP(s
+ 1, ==, e
) || s
[1] == '\n')
217 /* Rest are special begin-of-line cases */
223 if (PTRCMP(s
+ 4, <, e
) && s
[1] == 'r' && s
[2] == 'o' && s
[3] == 'm')
228 if (a
== XD
&& (PTRCMP(s
+ 1, ==, e
) || s
[1] == '\n'))
238 _qp_ctohex(char *store
, char c
)
242 store
[1] = _qp_itoa16
[(ui8_t
)c
& 0x0F];
243 c
= ((ui8_t
)c
>> 4) & 0x0F;
244 store
[0] = _qp_itoa16
[(ui8_t
)c
];
250 _qp_cfromhex(char const *hex
)
256 if ((i1
= (ui8_t
)hex
[0] - '0') >= NELEM(_qp_atoi16
) ||
257 (i2
= (ui8_t
)hex
[1] - '0') >= NELEM(_qp_atoi16
))
261 if ((i1
| i2
) & 0xF0u
)
275 _b64_decode_prepare(struct str
*work
, struct str
const *in
)
284 while (cp_len
> 0 && spacechar(*cp
))
288 for (cp
+= cp_len
; cp_len
> 0; --cp_len
) {
296 cp_len
= ((cp_len
* 3) >> 2) + (cp_len
>> 3);
303 _b64_decode(struct str
*out
, struct str
*in
)
307 ui8_t
const *q
, *end
;
310 p
= (ui8_t
*)out
->s
+ out
->l
;
311 q
= (ui8_t
const*)in
->s
;
313 for (end
= q
+ in
->l
; PTRCMP(q
+ 4, <=, end
);) {
314 ui32_t a
= _B64_DECUI8(q
[0]), b
= _B64_DECUI8(q
[1]),
315 c
= _B64_DECUI8(q
[2]), d
= _B64_DECUI8(q
[3]);
318 if (a
>= _B64_EQU
|| b
>= _B64_EQU
|| c
== _B64_BAD
|| d
== _B64_BAD
)
321 *p
++ = ((a
<< 2) | ((b
& 0x30) >> 4));
322 if (c
== _B64_EQU
) { /* got '=' */
327 *p
++ = (((b
& 0x0F) << 4) | ((c
& 0x3C) >> 2));
328 if (d
== _B64_EQU
) /* got '=' */
330 *p
++ = (((c
& 0x03) << 6) | d
);
335 size_t i
= PTR2SIZE((char*)p
- out
->s
);
340 in
->l
-= PTR2SIZE((char*)UNCONST(q
) - in
->s
);
347 mime_char_to_hexseq(char store
[3], char c
)
352 rv
= _qp_ctohex(store
, c
);
358 mime_hexseq_to_char(char const *hex
)
363 rv
= _qp_cfromhex(hex
);
369 mime_enc_target(void)
375 if ((cp
= ok_vlook(encoding
)) == NULL
)
376 rv
= MIME_DEFAULT_ENCODING
;
377 else if (!asccasecmp(cp
, "quoted-printable"))
379 else if (!asccasecmp(cp
, "8bit"))
381 else if (!asccasecmp(cp
, "base64"))
384 n_err(_("Warning: invalid *encoding*, using Base64: \"%s\"\n"), cp
);
392 mime_enc_from_ctehead(char const *hbody
)
397 if (hbody
== NULL
|| _is_ct_enc(hbody
, "7bit"))
399 else if (_is_ct_enc(hbody
, "8bit"))
401 else if (_is_ct_enc(hbody
, "base64"))
403 else if (_is_ct_enc(hbody
, "binary"))
405 else if (_is_ct_enc(hbody
, "quoted-printable"))
414 mime_enc_from_conversion(enum conversion
const convert
) /* TODO booom */
420 case CONV_7BIT
: rv
= "7bit"; break;
421 case CONV_8BIT
: rv
= "8bit"; break;
422 case CONV_TOQP
: rv
= "quoted-printable"; break;
423 case CONV_TOB64
: rv
= "base64"; break;
424 default: rv
= ""; break;
431 mime_enc_mustquote(char const *ln
, size_t lnlen
, enum mime_enc_flags flags
)
437 for (rv
= 0, sol
= TRU1
; lnlen
> 0; sol
= FAL0
, ++ln
, --lnlen
)
438 switch (_mustquote(ln
, ln
+ lnlen
, sol
, flags
)) {
442 assert(flags
& MIMEEF_ISENCWORD
);
454 qp_encode_calc_size(size_t len
)
459 /* The worst case sequence is 'CRLF' -> '=0D=0A=\n\0'.
460 * However, we must be aware that (a) the output may span multiple lines
461 * and (b) the input does not end with a newline itself (nonetheless):
462 * LC_ALL=C awk 'BEGIN{
463 * for (i = 1; i < 100000; ++i) printf "\xC3\xBC"
465 * MAILRC=/dev/null LC_ALL=en_US.UTF-8 s-nail -nvvd \
466 * -Ssendcharsets=utf8 -s testsub ./LETTER */
468 lines
= bytes
/ QP_LINESIZE
;
472 /* Trailing hard NL may be missing, so there may be two lines.
473 * Thus add soft + hard NL per line and a trailing NUL */
474 lines
= (bytes
/ QP_LINESIZE
) + 1;
485 qp_encode_cp(struct str
*out
, char const *cp
, enum qpflags flags
)
492 out
= qp_encode(out
, &in
, flags
);
498 qp_encode_buf(struct str
*out
, void const *vp
, size_t vp_len
,
506 out
= qp_encode(out
, &in
, flags
);
513 qp_encode(struct str
*out
, struct str
const *in
, enum qpflags flags
)
515 bool_t sol
= (flags
& QP_ISHEAD
? FAL0
: TRU1
), seenx
;
521 if (!(flags
& QP_BUF
)) {
522 lnlen
= qp_encode_calc_size(in
->l
);
523 out
->s
= (flags
& QP_SALLOC
) ? salloc(lnlen
) : srealloc(out
->s
, lnlen
);
531 enum mime_enc_flags ef
= MIMEEF_ISHEAD
|
532 (flags
& QP_ISENCWORD
? MIMEEF_ISENCWORD
: 0);
534 for (seenx
= FAL0
, sol
= TRU1
; is
< ie
; sol
= FAL0
, ++qp
) {
535 enum _qact mq
= _mustquote(is
, ie
, sol
, ef
);
539 /* We convert into a single *encoded-word*, that'll end up in
540 * =?C?Q??=; quote '?' from when we're inside there on */
541 if (seenx
&& c
== '?')
550 qp
= _qp_ctohex(qp
, c
) + 1;
556 /* The body needs to take care for soft line breaks etc. */
557 for (lnlen
= 0, seenx
= FAL0
; is
< ie
; sol
= FAL0
) {
558 enum _qact mq
= _mustquote(is
, ie
, sol
, MIMEEF_NONE
);
561 if (mq
== N
&& (c
!= '\n' || !seenx
)) {
563 if (++lnlen
< QP_LINESIZE
- 1)
565 /* Don't write a soft line break when we're in the last possible
566 * column and either an LF has been written or only an LF follows, as
567 * that'll end the line anyway */
568 /* XXX but - ensure is+1>=ie, then??
569 * xxx and/or - what about resetting lnlen; that contra
570 * xxx dicts input==1 input line assertion, though */
571 if (c
== '\n' || is
== ie
|| is
[0] == '\n' || is
[1] == '\n')
581 if (lnlen
> QP_LINESIZE
- 3 - 1) {
588 qp
= _qp_ctohex(qp
, c
);
591 if (c
!= '\n' || !seenx
)
599 /* Enforce soft line break if we haven't seen LF */
600 if (in
->l
> 0 && *--is
!= '\n') {
606 out
->l
= PTR2SIZE(qp
- out
->s
);
607 out
->s
[out
->l
] = '\0';
613 qp_decode(struct str
*out
, struct str
const *in
, struct str
*rest
)
620 if (rest
!= NULL
&& rest
->l
!= 0) {
628 out
->s
= srealloc(out
->s
, out
->l
+ in
->l
+ 3);
633 /* Decoding encoded-word (RFC 2049) in a header field? */
638 if (PTRCMP(is
+ 1, >=, ie
)) {
642 c
= _qp_cfromhex(is
);
647 /* Invalid according to RFC 2045, section 6.7. Almost follow */
650 *oc[0] = '['; oc[1] = '?'; oc[2] = ']';
651 *oc += 3; 0xFFFD TODO
655 *oc
++ = (c
== '_' /* US */) ? ' ' : (char)c
;
657 goto jleave
; /* XXX QP decode, header: errors not reported */
660 /* Decoding a complete message/mimepart body line */
669 * Therefore, when decoding a Quoted-Printable body, any
670 * trailing white space on a line must be deleted, as it will
671 * necessarily have been added by intermediate transport
673 for (; is
< ie
&& blankchar(*is
); ++is
)
675 if (PTRCMP(is
+ 1, >=, ie
)) {
676 /* Soft line break? */
683 /* Not a soft line break? */
685 c
= _qp_cfromhex(is
);
690 /* Invalid according to RFC 2045, section 6.7.
691 * Almost follow it and include the = and the follow char */
694 *oc[0] = '['; oc[1] = '?'; oc[2] = ']';
695 *oc += 3; 0xFFFD TODO
701 /* CRLF line endings are encoded as QP, followed by a soft line break, so
702 * check for this special case, and simply forget we have seen one, so as
703 * not to end up with the entire DOS file in a contiguous buffer */
705 if (oc
> os
&& oc
[-1] == '\n') {
706 #if 0 /* TODO qp_decode() we do not normalize CRLF
707 * TODO to LF because for that we would need
708 * TODO to know if we are about to write to
709 * TODO the display or do save the file!
710 * TODO 'hope the MIME/send layer rewrite will
711 * TODO offer the possibility to DTRT */
712 if (oc
- 1 > os
&& oc
[-2] == '\r') {
719 out
->l
= PTR2SIZE(oc
- os
);
720 rest
->s
= srealloc(rest
->s
, rest
->l
+ out
->l
);
721 memcpy(rest
->s
+ rest
->l
, out
->s
, out
->l
);
726 /* XXX RFC: QP decode should check no trailing WS on line */
728 out
->l
= PTR2SIZE(oc
- os
);
735 b64_encode_calc_size(size_t len
)
739 len
+= (((len
/ B64_ENCODE_INPUT_PER_LINE
) + 1) * 3);
740 len
+= 2 + 1; /* CRLF, \0 */
746 b64_encode(struct str
*out
, struct str
const *in
, enum b64flags flags
)
753 assert(!(flags
& B64_NOPAD
) ||
754 !(flags
& (B64_CRLF
| B64_LF
| B64_MULTILINE
)));
756 p
= (ui8_t
const*)in
->s
;
758 if (!(flags
& B64_BUF
)) {
759 i
= b64_encode_calc_size(in
->l
);
760 out
->s
= (flags
& B64_SALLOC
) ? salloc(i
) : srealloc(out
->s
, i
);
764 if (!(flags
& (B64_CRLF
| B64_LF
)))
765 flags
&= ~B64_MULTILINE
;
767 for (lnlen
= 0, i
= (ssize_t
)in
->l
; i
> 0; p
+= 3, i
-= 3) {
768 ui32_t a
= p
[0], b
, c
;
770 b64
[0] = _b64_enctbl
[a
>> 2];
773 b64
[1] = _b64_enctbl
[((a
& 0x3) << 4)];
779 b64
[1] = _b64_enctbl
[((a
& 0x03) << 4) | ((b
& 0xF0u
) >> 4)];
780 b64
[2] = _b64_enctbl
[((b
& 0x0F) << 2)];
786 b64
[1] = _b64_enctbl
[((a
& 0x03) << 4) | ((b
& 0xF0u
) >> 4)];
787 b64
[2] = _b64_enctbl
[((b
& 0x0F) << 2) | ((c
& 0xC0u
) >> 6)];
788 b64
[3] = _b64_enctbl
[c
& 0x3F];
793 if (!(flags
& B64_MULTILINE
))
796 if (lnlen
< B64_LINESIZE
)
800 if (flags
& B64_CRLF
)
802 if (flags
& (B64_CRLF
| B64_LF
))
806 if ((flags
& (B64_CRLF
| B64_LF
)) &&
807 (!(flags
& B64_MULTILINE
) || lnlen
!= 0)) {
808 if (flags
& B64_CRLF
)
810 if (flags
& (B64_CRLF
| B64_LF
))
812 } else if (flags
& B64_NOPAD
)
813 while (b64
!= out
->s
&& b64
[-1] == '=')
816 out
->l
= PTR2SIZE(b64
- out
->s
);
817 out
->s
[out
->l
] = '\0';
819 /* Base64 includes + and /, replace them with _ and -.
820 * This is base64url according to RFC 4648, then. Since we only support
821 * that for encoding and it is only used for boundary strings, this is
822 * yet a primitive implementation; xxx use tables; support decoding */
823 if (flags
& B64_RFC4648URL
) {
826 for (b64
= out
->s
; (c
= *b64
) != '\0'; ++b64
)
837 b64_encode_buf(struct str
*out
, void const *vp
, size_t vp_len
,
845 out
= b64_encode(out
, &in
, flags
);
852 b64_encode_cp(struct str
*out
, char const *cp
, enum b64flags flags
)
859 out
= b64_encode(out
, &in
, flags
);
866 b64_decode(struct str
*out
, struct str
const *in
, struct str
*rest
)
871 int rv
; /* XXX -> bool_t */
874 len
= _b64_decode_prepare(&work
, in
);
877 /* TODO B64_T is different since we must not fail for errors; in v15.0 this
878 * TODO will be filter based and B64_T will have a different one than B64,
879 * TODO for now special treat this all-horror */
881 /* With B64_T there may be leftover decoded data for iconv(3), even if
882 * that means it's incomplete multibyte character we have to copy over */
883 /* TODO strictly speaking this should not be handled in here,
884 * TODO since its leftover decoded data from an iconv(3);
885 * TODO In v15.0 this path will be filter based, each filter having its
886 * TODO own buffer for such purpose; for now we are BUSTED since for
887 * TODO Base64 rest is owned by iconv(3) */
891 rest
->s
= x
; /* Just for ownership reasons (all TODO in here..) */
896 out
->s
= srealloc(out
->s
, len
+1);
899 if (_b64_decode(out
, &work
) >= 0) {
905 /* Partial/False last sequence. TODO not solvable for non-EOF;
906 * TODO yes, invalid, but seen in the wild and should be handled,
907 * TODO but for that we had to have our v15.0 filter which doesn't
908 * TODO work line based but content buffer based */
909 if ((len
= work
.l
) <= 4) {
911 case 4: /* FALLTHRU */
912 case 3: x
[2] = '?'; /* FALLTHRU */
913 case 2: x
[1] = '?'; /* FALLTHRU */
914 default: x
[0] = '?'; break;
920 /* TODO Bad content: this problem is not solvable! I've seen
921 * TODO messages which broke lines in the middle of a Base64
922 * TODO tuple, followed by an invalid character ("!"), the follow
923 * TODO line starting with whitespace and the remaining sequence.
924 * TODO OpenSSL bailed, mutt(1) got it right (silently..).
925 * TODO Since "rest" is not usable by us, we cannot continue
926 * TODO sequences. We will be able to do so with the v15.0 filter
927 * TODO approach, if we */
928 /* Bad content: skip over a single sequence */
935 ui8_t bc
= (ui8_t
)*++work
.s
;
936 ui32_t state
= _B64_DECUI8(bc
);
938 if (state
!= _B64_EQU
&& state
!= _B64_BAD
)
947 /* Ignore an empty input, as may happen for an empty final line */
949 out
->s
= srealloc(out
->s
, 1);
951 } else if (work
.l
>= 4 && !(work
.l
& 3)) {
952 out
->s
= srealloc(out
->s
, len
+1);
953 if ((ssize_t
)(len
= _b64_decode(out
, &work
)) < 0)
960 out
->s
[out
->l
] = '\0';
965 char const *err
= _("[Invalid Base64 encoding]\n");
966 out
->l
= len
= strlen(err
);
967 out
->s
= srealloc(out
->s
, len
+1);
968 memcpy(out
->s
, err
, len
);