/** @file Parser: base64 functions impl. Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com) Authors: Konstantin Morshnev , Alexandr Petrosian */ #include "pa_base64.h" #include "pa_common.h" volatile const char * IDENT_PA_BASE64_C="$Id: pa_base64.C,v 1.15 2026/04/25 13:38:46 moko Exp $" IDENT_PA_BASE64_H; /* * BASE64 part inspired by g_mime_utils * Authors: Michael Zucchi * Jeffrey Stedfast * * Copyright 2000-2004 Ximian, Inc. (www.ximian.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * */ static const char *base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const char *base64_alphabet_url_safe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; Base64Options::Base64Options(bool awrap): strict(false), wrap(awrap), pad(true), abc(base64_alphabet) {} void Base64Options::set_url_safe_abc() { abc = base64_alphabet_url_safe; } #define BASE64_GROUPS_IN_LINE 19 static size_t pa_base64_encode(const uchar *in, size_t inlen, uchar *out, Base64Options options) { const uchar *inptr = in; uchar *outptr = out; const uchar *abc = (const uchar *)options.abc; if (inlen > 2) { const uchar *inend = in + inlen - 2; int already=0; while (inptr < inend) { int c1 = *inptr++; int c2 = *inptr++; int c3 = *inptr++; *outptr++ = abc[c1 >> 2]; *outptr++ = abc[(c2 >> 4) | ((c1 & 0x3) << 4)]; *outptr++ = abc[((c2 & 0x0f) << 2) | (c3 >> 6)]; *outptr++ = abc[c3 & 0x3f]; if ((++already) >= BASE64_GROUPS_IN_LINE && options.wrap) { *outptr++ = '\n'; already = 0; } } inlen = 2 - (inptr - inend); } if (inlen == 2) { int c1 = *inptr++; int c2 = *inptr++; outptr[0] = abc[c1 >> 2]; outptr[1] = abc[c2 >> 4 | ((c1 & 0x3) << 4)]; outptr[2] = abc[(c2 & 0x0f) << 2]; if(options.pad) { outptr[3] = '='; outptr += 4; } else { outptr += 3; } } else if (inlen == 1) { int c1 = *inptr++; outptr[0] = abc[c1 >> 2]; outptr[1] = abc[(c1 & 0x3) << 4]; if(options.pad) { outptr[2] = '='; outptr[3] = '='; outptr += 4; } else { outptr += 2; } } *outptr='\0'; return outptr - out; } static uchar gmime_base64_rank[256] = { 255,255,255,255,255,255,255,255,255,254,254,255,255,254,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 254,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,253,255,255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, }; static uchar gmime_base64_rank_url_safe[256] = { 255,255,255,255,255,255,255,255,255,254,254,255,255,254,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 254,255,255,255,255,255,255,255,255,255,255,255,255, 62,255,255, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,253,255,255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255, 63, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, }; size_t pa_base64_decode(const uchar *in, size_t inlen, uchar *out, Base64Options options) { const uchar *inptr = in; uchar *outptr = out; const uchar *inend = in + inlen; int saved = 0; int state = 0; uchar *abc_rank = options.abc == base64_alphabet ? gmime_base64_rank : gmime_base64_rank_url_safe; /* convert 4 base64 bytes to 3 normal bytes */ while (inptr < inend) { uchar c = abc_rank[*inptr++]; switch(c) { case 255: // non-base64 and non-whitespace chars. not allowed in strict mode if(options.strict) throw Exception(BASE64_FORMAT, 0, "Invalid base64 char on position %d is detected", inptr - in - 1); case 254: // whitespace chars 0x09, 0x0A, 0x0D, 0x20 are allowed in any mode break; case 253: // = if(state < 2) { if(options.strict) throw Exception(BASE64_FORMAT, 0, "Unexpected '=' on position %d is detected", inptr - in - 1); break; } if(state == 2) { // double '=' if(inptr == inend) { if(options.strict) throw Exception(BASE64_FORMAT, 0, "Unexpected end of chars"); break; } if(*inptr != '=') { if(options.strict) throw Exception(BASE64_FORMAT, 0, "Unexpected '=' on position %d is detected", inptr - in - 1); break; } inptr++; *outptr++ = (uchar)(saved >> 4); } else { // single '=' *outptr++ = (uchar)(saved >> 10); *outptr++ = (uchar)(saved >> 2); } state = 0; break; default: saved = (saved << 6) | c; state++; if (state == 4) { *outptr++ = (uchar)(saved >> 16); *outptr++ = (uchar)(saved >> 8); *outptr++ = (uchar)(saved); state = 0; } } } if(state > 0) { if(state > 1) { if(options.pad && options.strict) throw Exception(BASE64_FORMAT, 0, "Unexpected end of chars"); if(state == 2) { *outptr++ = (uchar)(saved >> 4); } else { *outptr++ = (uchar)(saved >> 10); *outptr++ = (uchar)(saved >> 2); } } else { if(options.strict) throw Exception(BASE64_FORMAT, 0, "Unexpected end of chars"); } } *outptr='\0'; // for text files return outptr - out; } char* pa_base64_encode(const char *in, size_t in_size, Base64Options options) { size_t new_size = ((in_size / 3 + 1) * 4); if (options.wrap) new_size += new_size / (BASE64_GROUPS_IN_LINE * 4) /*new lines*/; char* result = new(PointerFreeGC) char[new_size + 1 /*zero terminator*/]; PA_UNUSED size_t filled = pa_base64_encode((const uchar*)in, in_size, (uchar*)result, options); assert(filled <= new_size); return result; } size_t pa_base64_decode(const char *in, size_t in_size, char*& result, Base64Options options) { // every 4 base64 bytes are converted into 3 normal bytes size_t new_size = (in_size + 3) / 4 * 3; result = new(PointerFreeGC) char[new_size + 1 /*terminator*/]; return pa_base64_decode((const uchar*)in, in_size, (uchar*)result, options); }