2

I'm trying to write a program in c++ that can encode images into base64 and also decode base64 into images. I believe the encoder function is working fine and some websites can take the base64 code I generate and decode it into the image fine, but for some reason once I decode the base64 into a string and then write it to a file and save it as a png it says it can't be opened in an image viewer.

I confirmed that the string that is being written to the new file is exactly the same as the existing file (when opened in a text editor), but for some reason, the new file can't be opened but the existing one can be. I have even tried just making a new file in a text editor, and copying the text from the old file into it, but it still doesn't open in an image viewer.

I believe that both of the encode functions and the base64 decode function all work fine. I think the problem is in the Image Decode function.

Image Encode Function

string base64_encode_image(const string& path) {
    vector<char> temp;

    std::ifstream infile;
    infile.open(path, ios::binary);     // Open file in binary mode
    if (infile.is_open()) {
        while (!infile.eof()) {
            char c = (char)infile.get();
            temp.push_back(c);
        }
        infile.close();
    }
    else return "File could not be opened";
    string ret(temp.begin(), temp.end() - 1);
    ret = base64_encode((unsigned const char*)ret.c_str(), ret.size());

    return ret;
}

Image Decode Function

void base64_decode_image(const string& input) {
    ofstream outfile;
    outfile.open("test.png", ofstream::out);

    string temp = base64_decode(input);

    outfile.write(temp.c_str(), temp.size());
    outfile.close();
    cout << "file saved" << endl;
}

Encode Function base64

string base64_encode(unsigned const char* input, unsigned const int len) {
    string ret;
    size_t i = 0;
    unsigned char bytes[3];
    unsigned char sextets[4];

    while (i <= (len - 3)) {
        bytes[0] = *(input++);
        bytes[1] = *(input++);
        bytes[2] = *(input++);

        sextets[0] = (bytes[0] & 0xfc) >> 2;    // Cuts last two bits off of first byte
        sextets[1] = ((bytes[0] & 0x03) << 4) + ((bytes[1] & 0xf0) >> 4);   // Takes last two bits from first byte and adds it to first 4 bits of 2nd byte
        sextets[2] = ((bytes[1] & 0x0f) << 2) + ((bytes[2] & 0xc0) >> 6);   // Takes last 4 bits of 2nd byte and adds it to first 2 bits of third byte
        sextets[3] = bytes[2] & 0x3f;   // takes last 6 bits of third byte

        for (size_t j = 0; j < 4; ++j) {
            ret += base64_chars[sextets[j]];
        }

        i += 3; // increases to go to third byte
    }

    if (i != len) {
        size_t k = 0;
        size_t j = len - i; // Find index of last byte
        while (k < j) {     // Sets first bytes
            bytes[k] = *(input++);
            ++k;
        }

        while (j < 3) {     // Set last bytes to 0x00
            bytes[j] = '\0';
            ++j;
        }

        sextets[0] = (bytes[0] & 0xfc) >> 2;    // Cuts last two bits off of first byte
        sextets[1] = ((bytes[0] & 0x03) << 4) + ((bytes[1] & 0xf0) >> 4);   // Takes last two bits from first byte and adds it to first 4 bits of 2nd byte
        sextets[2] = ((bytes[1] & 0x0f) << 2) + ((bytes[2] & 0xc0) >> 6);   // Takes last 4 bits of 2nd byte and adds it to first 2 bits of third byte
        // No last one is needed, because if there were 4, then (i == len) == true

        for (j = 0; j < (len - i) + 1; ++j) {   // Gets sextets that include data
            ret += base64_chars[sextets[j]];    // Appends them to string
        }

        while ((j++) < 4)   // Appends remaining ='s
            ret += '=';

    }

    return ret;

}

Decode Function base64

string base64_decode(const string& input) {
    string ret;
    size_t i = 0;
    unsigned char bytes[3];
    unsigned char sextets[4];

    while (i < input.size() && input[i] != '=') {
        size_t j = i % 4;   // index per sextet
        if (is_base64(input[i])) sextets[j] = input[i++];       // set sextets with characters from string
        else { cerr << "Non base64 string included in input (possibly newline)" << endl; return ""; }
        if (i % 4 == 0) {
            for (j = 0; j < 4; ++j)             // Using j as a seperate index (not the same as it was originally used as, will later be reset)
                sextets[j] = indexof(base64_chars, strlen(base64_chars), sextets[j]);   // Change value to indicies of b64 characters and not ascii characters

            bytes[0] = (sextets[0] << 2) + ((sextets[1] & 0x30) >> 4);  // Similar bitshifting to before
            bytes[1] = ((sextets[1] & 0x0f) << 4) + ((sextets[2] & 0x3c) >> 2);
            bytes[2] = ((sextets[2] & 0x03) << 6) + sextets[3];

            for (j = 0; j < 3; ++j)     // Using j seperately again to iterate through bytes and adding them to full string
                ret += bytes[j];
        }
    }

    if (i % 4 != 0) {
        for (size_t j = 0; j < (i % 4); ++j)
            sextets[j] = indexof(base64_chars, strlen(base64_chars), sextets[j]);

        bytes[0] = (sextets[0] << 2) + ((sextets[1] & 0x30) >> 4);  // Similar bitshifting to before
        bytes[1] = ((sextets[1] & 0x0f) << 4) + ((sextets[2] & 0x3c) >> 2);
        for (size_t j = 0; j < (i % 4) - 1; ++j)
            ret += bytes[j];        // Add final bytes
    }
    return ret;
}

When I try to open the files produced by Image decode function It says that the file format isn't supported, or that it has been corrupted. The base64 produced by the encode function that I'm trying to decode is in this link https://pastebin.com/S5D90Fs8

Rory Hemmings
  • 33
  • 1
  • 4
  • Minor point but `while (i <= (len - 3))` fails if `len` is less than 3. `while (i + 3 <= len)` is better. – john Jul 16 '19 at 05:40
  • Why do you copy `temp` into a string in `base64_encode_image` before passing it to the base64 encoder? – 1201ProgramAlarm Jul 16 '19 at 05:43
  • I see that your code reads one too many characters and then removes that extra character from your input string. It would be simpler to read the correct number of characters in the first place. Like this `char c; while (infile.get(c)) {`. Now you don't need `string ret(temp.begin(), temp.end() - 1);` at the end. [More on this](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-i-e-while-stream-eof-cons) – john Jul 16 '19 at 05:52

1 Answers1

2

When you open outfile in base64_decode_image, you do not specify the ofstream::binary flag like you do in base64_encode_image when reading the image. Without that flag, you're writing in text mode which can alter the data you're writing (when adjusting for newlines).

1201ProgramAlarm
  • 30,320
  • 7
  • 40
  • 49
  • This worked immediately. Thank you for your response. – Rory Hemmings Jul 16 '19 at 06:28
  • @RoryHemmings If this is the correct answer to your problem, please consider accepting it as such - by clicking the hollow tick (checkmark) beside the vote count then not only will everyone know it is correct, but the person who helped you will also get recognised with reputation points. You can also upvote as well as accept useful answers. – Mark Setchell Jul 17 '19 at 13:17