Tell me more ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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));

}
share|improve this question

closed as off topic by Corbin, Jeff Vanzella, Brian Reichle, Quentin Pradet, James Khoury Sep 22 '12 at 5:23

Questions on Code Review Stack Exchange are expected to relate to code review request within the scope defined in the FAQ. Consider editing the question or leaving comments for improvement if you believe the question can be reworded to fit within the scope. Read more about closed questions here.

1 Answer

FOA, code looks clear and clean, well indented and formatted. Thumbs up!!

Now, to the comments:

  • Some of the variable names might have more significant names: is can be changed to input_stream, for instance.
  • File size: it looks like you try to get the file size out of the stream by fetching the stream until reaching its end. I'd change that to an explicit call to a function provider by the hosting OS' API (see stat), or by a library that implements it, like boost.
  • Extract file size calculation to a function, to achieve code reuse.
  • Base64 format should convert a byte array (or a binary stream) to an ASCII string. It has nothing to do with Bitmap format in specific. I'd recommend you create some test cases using a C++ unit testing framework (there are some) and find the bug, if such exists.

Good luck!

share|improve this answer

Not the answer you're looking for? Browse other questions tagged or ask your own question.