cipher/base/base64.cpp

253 lines
6.7 KiB
C++

/*
** Copyright (C) 2014 Wang Yaofu
** All rights reserved.
**
**Author:Wang Yaofu voipman@qq.com
**Description: The source file of base64.
*/
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "base64.h"
namespace {
static const unsigned char EncodeTable[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const unsigned char* DecodeTable = NULL;
void FillDecodeTable() {
// double buffer is used here for thread safe.
static unsigned char DecodeTableBuff[256];
unsigned char buff[256];
if (DecodeTable != NULL) {
return;
}
::memset(buff, 0x80, sizeof(buff));
for (size_t k = 0; k < sizeof(EncodeTable); ++k) {
buff[(size_t)EncodeTable[k]] = k;
}
// to mark those valid characters in encoded string, but not in these
// 64 bases list.
buff[(size_t)'\r'] = buff[(size_t)'\n'] = 0x4F;
buff[(size_t)'='] = 0x40;
::memcpy(DecodeTableBuff, buff, sizeof(DecodeTableBuff));
DecodeTable = DecodeTableBuff;
}
// Get the next 4 characters from input string, '\r\n' will be trimmed off.
// The input string starts from 'p', and ends before 'q'. 'buff' is for
// storing the return characters.
// The return value, -1: error, there aren't 4 characters available, or get
// invalid character. 0-4 mean the number of valid characters, '=' is excluded.
int GetNext4EncodedCharacters(const unsigned char*& p,
const unsigned char* q,
unsigned char* buff) {
int k = 0;
unsigned char c = 0;
while (k < 4 && p < q) {
c = DecodeTable[*p];
if ((c & 0xC0) == 0) { // normal valid characters
*buff++ = c;
++p;
++k;
} else if (c & 0x80) { // not ('\r' or '\n' or '=')
return -1;
} else if (*p == '=') {
break;
} else { // ('\r' or '\n')
++p;
}
}
// success. this should be most of the cases, return as soon as possible
if (k == 4) {
return 4;
}
// get a '='
if (p < q && *p == '=') {
++p;
// there should be 4 characters in the last encode group
int tail = 4 - k - 1;
// there should not be more than 2 '=' in the end
if (tail > 1) {
return -1;
}
while (tail > 0 && p < q && ((DecodeTable[*p] & 0x40) == 0x40)) {
if (*p == '=') {
--tail;
}
++p;
}
// any character not ('\r' or '\n' or '=') appears after '='
if (tail != 0) {
return -1;
}
// only ('\r' || '\n') is allowed at the end
while (p < q) {
if ((DecodeTable[*p] & 0x4F) == 0x4F) {
++p;
} else {
return -1;
}
}
return k;
}
// for ('\r' or '\n') at very end
while (p < q && (DecodeTable[*p] & 0x4F) == 0x4F) {
++p;
}
if (k == 0 && p == q) {
return 0;
}
return -1;
}
size_t ExpectedEncodeLength(size_t len)
{
size_t encodedLen = ((len * 4 / 3 + 3) / 4) * 4;
return encodedLen;
}
size_t ExpectedDecodeLength(size_t len)
{
return (size_t)((len + 3) / 4 * 3);
}
} // anonymous namespace
bool Base64Encode(const std::string& input, std::string* output)
{
assert(output);
output->resize(ExpectedEncodeLength(input.size()));
char* buff = const_cast<char*>(output->data());
size_t len = output->size();
if (!Base64Encode(input, buff, &len)) {
output->clear();
return false;
}
output->resize(len);
return true;
}
bool Base64Encode(const std::string& input, char* output, size_t* len)
{
assert(output);
char* buff = output;
if (__builtin_expect(*len < ExpectedEncodeLength(input.size()), 0)) {
return false;
}
unsigned char *p = (unsigned char*)input.data();
unsigned char *q = p + input.size();
unsigned char c1, c2, c3;
// process 3 char every loop
for (; p + 3 <= q; p += 3) {
c1 = *p;
c2 = *(p + 1);
c3 = *(p + 2);
*buff++ = EncodeTable[c1 >> 2];
*buff++ = EncodeTable[((c1 << 4) | (c2 >> 4)) & 0x3f];
*buff++ = EncodeTable[((c2 << 2) | (c3 >> 6)) & 0x3f];
*buff++ = EncodeTable[c3 & 0x3f];
}
// the reminders
if (q - p == 1) {
c1 = *p;
*buff++ = EncodeTable[(c1 & 0xfc) >> 2];
*buff++ = EncodeTable[(c1 & 0x03) << 4];
*buff++ = '=';
*buff++ = '=';
} else if (q - p == 2) {
c1 = *p;
c2 = *(p + 1);
*buff++ = EncodeTable[(c1 & 0xfc) >> 2];
*buff++ = EncodeTable[((c1 & 0x03) << 4) | ((c2 & 0xf0) >> 4)];
*buff++ = EncodeTable[((c2 & 0x0f) << 2)];
*buff++ = '=';
}
*len = buff - output;
return true;
}
bool Base64Decode(const std::string& input, std::string* output)
{
assert(output);
output->resize(ExpectedDecodeLength(input.size()));
char* buff = const_cast<char*>(output->data());
size_t len = output->size();
if (!Base64Decode(input, buff, &len)) {
output->clear();
return false;
}
output->resize(len);
return true;
}
bool Base64Decode(const std::string& input, char* output, size_t* len)
{
assert(output && len);
char* buff = output;
if (__builtin_expect(*len < ExpectedDecodeLength(input.size()), 0)) {
return false;
}
if (__builtin_expect(!DecodeTable, 0)) {
FillDecodeTable();
}
if (input.empty()) {
*len = buff - output;
return true;
}
const unsigned char* p = (unsigned char*)input.data();
const unsigned char* q = (unsigned char*)input.data() + input.size();
// handle 4 bytes in every loop
while (true) {
char ch = 0;
unsigned char encoded[4];
int len = GetNext4EncodedCharacters(p, q, encoded);
if (__builtin_expect(len == 4, 1)) {
ch = encoded[0] << 2; // all 6 bits
ch |= encoded[1] >> 4; // 2 high bits
*buff++ = ch;
ch = encoded[1] << 4; // 4 low bits
ch |= encoded[2] >> 2; // 4 high bits
*buff++ = ch;
ch = encoded[2] << 6; // 2 low bits
ch |= encoded[3];
*buff++ = ch;
} else if (len >= 2) {
ch = encoded[0] << 2; // all 6 bits
ch |= encoded[1] >> 4; // 2 high bits
*buff++ = ch;
if (len == 3) {
ch = encoded[1] << 4; // 4 low bits
ch |= encoded[2] >> 2; // 4 high bits
*buff++ = ch;
}
} else if (len == 0) {
break;
} else {
return false;
}
}
*len = buff - output;
return true;
}