I've written a Base64 encoder/decoder. It works great for plain text. When I pass it a 24-bit .bmp it successfully and correctly encodes it, but when I attempt to decode it the file header comes back correct, but the image data is slightly off where, as an example, a 0xFF00FF triplet of bytes describing magenta comes back as 0x0300FF, a shade of red (and a similar errors for the rest of the data), not what I want. Even though the file is back to a well-formed .bmp and can be loaded by various programs the colors are incorrect. I think it has something to do with the presence of NULL bytes in the image data that is throwing the decoding process off but I can't be sure.
As a second question: Is there a way to get the total number of bytes in a file without having to manually traverse the entire file (without using the windows.h header or any third party non-STL libraries)?
#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>
#include <map>
#include <utility>
char EncodingTable[64] = {'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', //0-25
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', //26-51
'0','1','2','3','4','5','6','7','8','9', //52-61
'+','/'}; //62-63
char PADDING_CHAR = '=';
typedef std::map<char, int> DecodingMap;
DecodingMap DecodingTable;
enum DECODING_MASK {
ALL_SIX = 0x3F,
MOST_SIGNIFICANT_TWO = 0x30,
LEAST_SIGNIFICANT_TWO = 0x03,
MOST_SIGNIFICANT_FOUR = 0x3C,
LEAST_SIGNIFICANT_FOUR = 0x0F,
};
//ENCODING FUNCTIONS
int GetFirstSymbolIndex(char* encoding_buffer);
int GetSecondSymbolIndex(char* encoding_buffer);
int GetThirdSymbolIndex(char* encoding_buffer);
int GetFourthSymbolIndex(char* encoding_buffer);
char GetFirstSymbol(char* encoding_buffer);
char GetSecondSymbol(char* encoding_buffer);
char GetThirdSymbol(char* encoding_buffer);
char GetFourthSymbol(char* encoding_buffer);
//DECODING FUNCTIONS
void BuildDecodingTable(DecodingMap& table);
char GetFirstByte(char* decoding_buffer);
char GetSecondByte(char* decoding_buffer);
char GetThirdByte(char* decoding_buffer);
std::string Encode(std::string file);
std::string Encode(std::istream& file);
std::string Decode(std::string file);
std::string Decode(std::istream& file);
std::string DECODE(".base64");
int main(int argc, char** argv) {
if(argc < 2) {
std::cout << "No files selected." << std::endl;
return 1;
}
BuildDecodingTable(DecodingTable);
for(int i = 1; i <= argc - 1; ++i) {
std::string s(argv[i]);
s = s.substr(s.find_last_of('.'));
if(s != DECODE) {
std::cout << "Encoding " << argv[i] << std::endl;
std::string output_string;
output_string = Encode(argv[i]);
std::ofstream output;
std::string output_file(argv[i]);
std::string extension = ".base64";
output_file.append(extension);
output.open(output_file, std::ios_base::binary);
output << output_string;
output.close();
} else {
std::cout << "Decoding " << argv[i] << std::endl;
std::string output_string;
output_string = Decode(argv[i]);
std::ofstream output;
std::string output_file(argv[i]);
output_file = output_file.substr(0, output_file.find_last_of('.'));
output.open(output_file, std::ios_base::binary);
output << output_string;
output.close();
}
}
return 0;
}
//ENCODING
int GetFirstSymbolIndex(char* encoding_buffer) {
return (encoding_buffer[0] >> 2);
}
int GetSecondSymbolIndex(char* encoding_buffer) {
return (((encoding_buffer[0] & 0x03) << 4) | ((encoding_buffer[1] & 0xF0) >> 4));
}
int GetThirdSymbolIndex(char* encoding_buffer) {
return (((encoding_buffer[1] & 0x0F) << 2) | ((encoding_buffer[2] & 0xC0) >> 6));
}
int GetFourthSymbolIndex(char* encoding_buffer) {
return (encoding_buffer[2] & 0x3F);
}
//Gets the 6 most significant digits of the first byte.
char GetFirstSymbol(char* encoding_buffer) {
return EncodingTable[GetFirstSymbolIndex(encoding_buffer)];
}
//Gets the 2 least significant digits from previous (first) byte and 4 most significant from the second byte.
char GetSecondSymbol(char* encoding_buffer) {
return EncodingTable[GetSecondSymbolIndex(encoding_buffer)];
}
//Gets the 4 least significant digits from previous (second) byte and 2 least significant from the third byte.
char GetThirdSymbol(char* encoding_buffer) {
return EncodingTable[GetThirdSymbolIndex(encoding_buffer)];
}
//Gets the 6 least significant digits from the third byte.
char GetFourthSymbol(char* encoding_buffer) {
return EncodingTable[GetFourthSymbolIndex(encoding_buffer)];
}
//DECODING
//First Byte is all 6 of first symbol and 2 most significant bits of second symbol.
char GetFirstByte(char* decoding_buffer) {
DecodingMap::iterator first_iter = DecodingTable.find(decoding_buffer[0]);
DecodingMap::iterator second_iter = DecodingTable.find(decoding_buffer[1]);
int first_index;
if(first_iter == DecodingTable.end()) {
first_index = decoding_buffer[0];
} else {
first_index = (*first_iter).second;
}
int second_index;
if(second_iter == DecodingTable.end()) {
second_index = decoding_buffer[1];
} else {
second_index = (*second_iter).second;
}
first_index = (first_index & ALL_SIX) << 2;
second_index = (second_index & MOST_SIGNIFICANT_TWO) >> 4;
int result = first_index | second_index;
return result;
}
//Second Byte is 4 least significant bits of second symbol and 4 most significant bits of third symbol.
char GetSecondByte(char* decoding_buffer) {
DecodingMap::iterator second_iter = DecodingTable.find(decoding_buffer[1]);
DecodingMap::iterator third_iter = DecodingTable.find(decoding_buffer[2]);
int second_index;
if(second_iter == DecodingTable.end()) {
second_index = decoding_buffer[1];
} else {
second_index = (*second_iter).second;
}
int third_index;
if(third_iter == DecodingTable.end()) {
third_index = decoding_buffer[2];
} else {
third_index = (*third_iter).second;
}
second_index = (second_index & LEAST_SIGNIFICANT_FOUR) << 4;
third_index = (third_index & MOST_SIGNIFICANT_FOUR) >> 2;
int result = second_index | third_index;
return result;
}
//Third Byte is 2 least significant bits of third symbol and all of fourth symbol.
char GetThirdByte(char* decoding_buffer) {
DecodingMap::iterator third_iter = DecodingTable.find(decoding_buffer[2]);
DecodingMap::iterator fourth_iter = DecodingTable.find(decoding_buffer[3]);
int third_index;
if(third_iter == DecodingTable.end()) {
third_index = decoding_buffer[2];
} else {
third_index = (*third_iter).second;
}
int fourth_index;
if(fourth_iter == DecodingTable.end()) {
fourth_index = decoding_buffer[3];
} else {
fourth_index = (*fourth_iter).second;
}
third_index = (third_index & LEAST_SIGNIFICANT_TWO) << 6;
fourth_index = fourth_index & ALL_SIX;
int result = third_index | fourth_index;
return result;
}
std::string Encode(std::string file) {
std::string output;
std::ifstream ifs;
ifs.open(file.c_str(), std::ios_base::binary);
output = Encode(ifs);
ifs.close();
return output;
}
std::string Decode(std::string file) {
std::string output;
std::ifstream ifs;
ifs.open(file.c_str(), std::ios_base::binary);
output = Decode(ifs);
ifs.close();
return output;
}
std::string Encode(std::istream& is) {
std::string output;
char count[1] = {'\0'};
unsigned long file_size = 0;
char encoding_buffer[3] = {'\0', '\0', '\0'};
while(is.fail() == false) {
is.read(reinterpret_cast<char*>(count), sizeof(count));
if(is.fail() == false) ++file_size;
}
is.clear();
is.seekg(0);
if(file_size == 0) {
is.clear();
return output;
}
while(is.fail() == false) {
is.read(reinterpret_cast<char*>(encoding_buffer), sizeof(encoding_buffer));
char firstsymbol = '\0';
char secondsymbol = '\0';
char thirdsymbol = '\0';
char fourthsymbol = '\0';
std::streamoff i = 0;
i = is.tellg();
//At EOF, check if encoding_buffer is missing one or more characters.
//First character is null. Empty encoding_buffer == evenly divisable, i.e. no need to continue.
if(i == -1 && encoding_buffer[0] == '\0') {
break;
}
//Second character is null. Only one in encoding_buffer.
if(i == -1 && encoding_buffer[1] == '\0') {
firstsymbol = GetFirstSymbol(encoding_buffer);
secondsymbol = GetSecondSymbol(encoding_buffer);
thirdsymbol = GetThirdSymbol(encoding_buffer);
fourthsymbol = GetFourthSymbol(encoding_buffer);
output.push_back(firstsymbol);
output.push_back(secondsymbol);
output.push_back(PADDING_CHAR);
output.push_back(PADDING_CHAR);
break;
}
//Third character is null. Only two in encoding_buffer.
if(i == -1 && encoding_buffer[2] == '\0') {
firstsymbol = GetFirstSymbol(encoding_buffer);
secondsymbol = GetSecondSymbol(encoding_buffer);
thirdsymbol = GetThirdSymbol(encoding_buffer);
fourthsymbol = GetFourthSymbol(encoding_buffer);
output.push_back(firstsymbol);
output.push_back(secondsymbol);
output.push_back(thirdsymbol);
output.push_back(PADDING_CHAR);
break;
}
firstsymbol = GetFirstSymbol(encoding_buffer);
secondsymbol = GetSecondSymbol(encoding_buffer);
thirdsymbol = GetThirdSymbol(encoding_buffer);
fourthsymbol = GetFourthSymbol(encoding_buffer);
output.push_back(firstsymbol);
output.push_back(secondsymbol);
output.push_back(thirdsymbol);
output.push_back(fourthsymbol);
encoding_buffer[0] = '\0';
encoding_buffer[1] = '\0';
encoding_buffer[2] = '\0';
}
is.clear();
return output;
}
std::string Decode(std::istream& is) {
std::string output;
char count[1] = {'\0'};
unsigned long file_size = 0;
char decoding_buffer[4] = {'\0', '\0', '\0', '\0'};
while(is.fail() == false) {
is.read(reinterpret_cast<char*>(count), sizeof(count));
if(is.fail() == false) ++file_size;
}
is.clear();
is.seekg(0);
if(file_size == 0) {
is.clear();
return output;
}
while(is.fail() == false) {
is.read(reinterpret_cast<char*>(decoding_buffer), sizeof(decoding_buffer));
char firstbyte = '\0';
char secondbyte = '\0';
char thirdbyte = '\0';
std::streamoff i = 0;
i = is.tellg();
//Check if Decoding Buffer missing one or more bytes.
//First character is PADDING_CHAR. Empty decoding_buffer == evenly divisable, i.e. no need to continue.
//SHOULD NEVER HAPPEN!
if(i == -1 && decoding_buffer[0] == PADDING_CHAR) {
break;
}
//Second character is PADDING_CHAR.
//SHOULD NEVER HAPPEN!
if(i == -1 && decoding_buffer[1] == PADDING_CHAR) {
break;
}
//Third character is PADDING_CHAR. Only one in decoding_buffer.
if(i == -1 && decoding_buffer[2] == PADDING_CHAR) {
firstbyte = GetFirstByte(decoding_buffer);
secondbyte = GetSecondByte(decoding_buffer);
thirdbyte = GetThirdByte(decoding_buffer);
output.push_back(firstbyte);
break;
}
//Fourth character is PADDING_CHAR. Only two in decoding_buffer.
if(i == -1 && decoding_buffer[3] == PADDING_CHAR) {
firstbyte = GetFirstByte(decoding_buffer);
secondbyte = GetSecondByte(decoding_buffer);
thirdbyte = GetThirdByte(decoding_buffer);
output.push_back(firstbyte);
output.push_back(secondbyte);
break;
}
firstbyte = GetFirstByte(decoding_buffer);
secondbyte = GetSecondByte(decoding_buffer);
thirdbyte = GetThirdByte(decoding_buffer);
output.push_back(firstbyte);
output.push_back(secondbyte);
output.push_back(thirdbyte);
decoding_buffer[0] = '\0';
decoding_buffer[1] = '\0';
decoding_buffer[2] = '\0';
decoding_buffer[3] = '\0';
}
is.clear();
return output;
}
void BuildDecodingTable(DecodingMap& table) {
table.clear();
char cur_char = 'A';
for(int i = 0; i < 26; ++i) {
table.insert(std::make_pair(cur_char++, i));
}
cur_char = 'a';
for(int i = 26; i < 52; ++i) {
table.insert(std::make_pair(cur_char++, i));
}
cur_char = '0';
for(int i = 52; i < 62; ++i) {
table.insert(std::make_pair(cur_char++, i));
}
table.insert(std::make_pair('+', 62));
table.insert(std::make_pair('/', 63));
}