yate/engine/Base64.cpp

280 lines
8.8 KiB
C++

/**
* Base64.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Base64 data encoding and decoding
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2014 Null Team
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*
* 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.
*/
#include "yateclass.h"
using namespace TelEngine;
// Padding char
#define PADDING_CHAR '='
static String s_eoln = "\r\n";
static String s_ignore = "=\r\n\t ";
// Base64 alphabet
// See RFC 4648 Table 1
static char s_alphabet[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#define IC 255
// ASCII to Base64 translation table
// Each element except for IC represents an index in s_alphabet
static unsigned char s_ato64[256] = {
IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,
IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,
IC,IC,IC,62,IC,IC,IC,63,52,53,54,55,56,57,58,59,60,61,IC,IC,
IC,IC,IC,IC,IC, 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,IC,IC,IC,IC,IC,IC,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,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,
IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,
IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,
IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,
IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,
IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,
IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC,IC
};
#undef IC
// Check in the translation table if 'ch' is a valid Base64 char
static inline bool valid(char ch)
{
return s_ato64[(unsigned char)ch] < 64;
}
// Check if 'ch' should be ignored
// Check in the translation table if 'ch' is a valid Base64 char
// Return -1 to ignore it, 1 to accept it, 0 on error
static inline int validLiberal(char ch)
{
for (unsigned int i = 0; i < s_ignore.length(); i++)
if (s_ignore.at(i) == ch)
return -1;
return valid(ch) ? 1 : 0;
}
// Add end of line to dest and increase index if lines is non 0 and end
// if line was reached
static inline void addEoln(String& dest, unsigned int& idx,
unsigned int& lines, unsigned int& crtLine, unsigned int lineLen)
{
if (!(lines && crtLine == lineLen))
return;
char* d = (char*)dest.c_str();
d[idx++] = s_eoln[0];
d[idx++] = s_eoln[1];
crtLine = 0;
lines--;
}
// Add an encoded Base64 char to a destination string after clearing
// the bits 6 and 7. Increase the string's index
// Add end of line to dest and increase index if lines is non 0 and end
// if line was reached
static inline void addEnc(String& dest, unsigned int& idx, unsigned char ch,
unsigned int& lines, unsigned int& crtLine, unsigned int lineLen)
{
((char*)dest.c_str())[idx++] = s_alphabet[ch & 0x3f];
addEoln(dest,idx,lines,crtLine,lineLen);
}
// Add a decoded char buffer to a destination buffer and increase the index
// Len must be is 2,3,4
static inline void addDec(DataBlock& dest, unsigned int& idx,
unsigned char* dec, unsigned int len)
{
unsigned char* d = ((unsigned char*)dest.data());
if (len == 4) {
d[idx++] = dec[0] << 2 | dec[1] >> 4;
d[idx++] = dec[1] << 4 | dec[2] >> 2;
d[idx++] = dec[2] << 6 | dec[3];
}
else if (len == 3) {
d[idx++] = dec[0] << 2 | dec[1] >> 4;
d[idx++] = dec[1] << 4 | dec[2] >> 2;
}
else
d[idx++] = dec[0] << 2 | dec[1] >> 4;
}
// Add a padding char to dest and increase index
static inline void addPadding(String& dest, unsigned int& idx,
unsigned int& lines, unsigned int& crtLine, unsigned int lineLen)
{
((char*)dest.c_str())[idx++] = PADDING_CHAR;
addEoln(dest,idx,lines,crtLine,lineLen);
}
// Encode this buffer to a destination string
void Base64::encode(String& dest, unsigned int lineLen, bool lineAtEnd)
{
dest = "";
if (!length())
return;
unsigned char* s = (unsigned char*)data(); // Source buffer
unsigned int rest = length() % 3; // The number of bytes that will need padding
unsigned int full = length() - rest; // The number of bytes in source that will
// be processed in 3-byte chunks
unsigned int i = 0; // Source index
unsigned int lines = 0; // Number of lines
unsigned int crtLine = 0; // Index in current line
unsigned int iDest = 0; // Destination index
unsigned int len = full / 3 * 4 + (rest ? 4 : 0); // Destination length, without EOLNs
// Calculate how many lines we need (except for the last one)
if (lineLen) {
lines = len / lineLen;
if (0 == (len % lineLen) && lines)
lines--;
}
dest.assign(PADDING_CHAR,len + lines * s_eoln.length());
DDebug("Base64",DebugAll,
"Encoding %u bytes (full=%u rest=%u) to %u bytes lines=%u",
length(),full,rest,dest.length(),lines);
// Encode each 3 bytes chunk from source to 4 bytes Base64 destination
// 1: s_alphabet[bits 2-7 from s[i]]
// 2: s_alphabet[bits 0,1 from s[i] + bits 4-7 from s[i+1]]
// 3: s_alphabet[bits 0-3 from s[i+1] + bits 6,7 from s[i+2]]
// 4: s_alphabet[bits 0-5 from s[i+2]]
for (; i < full; i += 3) {
addEnc(dest,iDest,s[i] >> 2,lines,crtLine,lineLen);
addEnc(dest,iDest,s[i] << 4 | s[i+1] >> 4,lines,crtLine,lineLen);
addEnc(dest,iDest,s[i+1] << 2 | s[i+2] >> 6,lines,crtLine,lineLen);
addEnc(dest,iDest,s[i+2],lines,crtLine,lineLen);
}
// Encode rest (can be 1 or 2) to 4 bytes destination
// 1: 2 chars + 2 padding, 2: 3 chars + 1 padding
// Don't add the final padding char: destination was filled with it
if (rest) {
addEnc(dest,iDest,s[i] >> 2,lines,crtLine,lineLen);
if (rest == 1)
addEnc(dest,iDest,s[i] << 4,lines,crtLine,lineLen);
else {
addEnc(dest,iDest,s[i] << 4 | s[i+1] >> 4,lines,crtLine,lineLen);
addEnc(dest,iDest,s[i+1] << 2,lines,crtLine,lineLen);
}
}
// Add final end of line ?
if (lineAtEnd)
dest << s_eoln;
}
// Decode this buffer to a destination one
bool Base64::decode(DataBlock& dest, bool liberal)
{
dest.clear();
// Calculate the number of alphabet characters
unsigned int full = 0;
unsigned int rest = 0;
unsigned char* src = (unsigned char*)data();
if (liberal)
for (unsigned int i = 0; i < length(); i++) {
int res = validLiberal(src[i]);
if (!res) {
Debug("Base64",DebugNote,"Got invalid char 0x%x at pos %u [%p]",src[i],i,this);
return false;
}
if (res > 0)
full++;
}
else {
full = length();
// Skip padding chars from end
for (; full; full--)
if (src[full-1] != PADDING_CHAR)
break;
}
// rest MUST be 0, 2 or 3
// rest is 1: can't build an 8-bit ascii char from a 6-bit Base64 char
rest = full % 4;
full -= rest;
if (!(full || rest) || rest == 1) {
Debug("Base64",DebugNote,"Got invalid length %u [%p]",length(),this);
return true;
}
dest.assign(0,full / 4 * 3 + (rest ? rest - 1 : 0));
DDebug("Base64",DebugAll,"Decoding %u bytes (full=%u rest=%u) to %u bytes",
length(),full,rest,dest.length());
unsigned int iDest = 0;
unsigned char dec[4];
if (!liberal) {
#define GET_DEC(a) \
if (valid(src[i+a])) \
dec[a] = s_ato64[src[i+a]]; \
else { \
Debug("Base64",DebugNote,"Got invalid char 0x%x at pos %u [%p]", \
src[i+a],i+a,this); \
return false; \
}
unsigned int i = 0;
// Decode each 4 bytes chunk from source
// Translate each byte and build 3 destination bytes from 4 6-bit Base64 chars
// 1: bits 0-5 from dec[0] + bits 4,5 from dec[1]
// 2: bits 0-3 from dec[1] + bits 2-5 from dec[2]
// 3: bits 0,1 from dec[2] + bits 0-5 from dec[3]
for (; i < full; i += 4) {
GET_DEC(0)
GET_DEC(1)
GET_DEC(2)
GET_DEC(3)
addDec(dest,iDest,dec,4);
}
// Process the rest
// 2: 1 destination byte. 3: 2 bytes in destination
if (rest) {
GET_DEC(0)
GET_DEC(1)
if (rest == 3) {
GET_DEC(2)
}
}
#undef GET_DEC
}
else {
unsigned int iDec = 0;
for (unsigned int i = 0; i < length(); i++, src++) {
int res = validLiberal(*src);
if (!res) {
Debug("Base64",DebugNote,"Got invalid char 0x%x at pos %u [%p]",*src,i,this);
return false;
}
if (res < 0)
continue;
dec[iDec++] = s_ato64[*src];
if (iDec == 4) {
addDec(dest,iDest,dec,4);
iDec = 0;
}
}
}
if (rest)
addDec(dest,iDest,dec,rest);
return true;
}
/* vi: set ts=8 sw=4 sts=4 noet: */