1
\$\begingroup\$

Base64 is a widely used encoding mechanism to allow arbitrary binary content to be transferred as printable text. This program is designed to take binary file as input and produce a Base64-encoded binary file as output.

#include <stdlib.h>
#include <stdio.h>

void enblock( int len, char *in, char *base64, FILE *outputFILE)
{
    fprintf(outputFILE, "%c", base64[in[0] >> 2]); 
    fprintf(outputFILE, "%c", base64[((in[0] & 3) << 4) | ((in[1] & 240) >> 4)]);
    if (len > 1)
    {
        fprintf(outputFILE,"%c", base64[((in[1] & 15) << 2) | ((in[2] & 192) >> 6)]);
    }
    else 
    {
        fprintf(outputFILE, "%c", '=');
    }
    if (len > 2)
    {
        fprintf(outputFILE, "%c", base64[(in[2] & 63)]);
    }
    else
    {
        fprintf(outputFILE, "%c",'=');
    }
}

void endecode (FILE *inputFILE, FILE *outputFILE)
{
    static char base64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    int len = 0;
    int i = 0;
    char buffer[3] = {0};
    while ((len = fread(buffer,sizeof(char),3,inputFILE)) >= 1)
    {   
        for (i = len; i < 3; i++)
            buffer[i] = 0;
        enblock(len, buffer, base64 ,outputFILE );
    }
}

int main() 
{
    FILE *file1 = NULL;
    FILE *file2 = NULL;
    if (NULL == (file1 = fopen("INPUT.txt", "r")))
    {   
        printf("Can't open INPUT file!");
        return -1;
    }
    if (NULL == (file2 = fopen("OUTPUT.base64", "wb")))
    {
        printf("Can't create OUTPUT file");
        return -1;
    }   
    endecode(file1,file2);
    fclose(file1);
    fclose(file2);
    return 0;
}
\$\endgroup\$
  • \$\begingroup\$ Shouldn't that be if (NULL != (file1 = fopen("INPUT.txt", "r")))? I also always put the assignment/function calls first. \$\endgroup\$ – Goswin von Brederlow Nov 23 '14 at 21:05
2
\$\begingroup\$

I see some things that could be improved.

Add comments to describe what the code is doing

It was not really immediately obvious what the code was intending to do. In part this was because of some of the variable names, but also due to lack of comments. Some comments describing what each function is attempting to do would have helped a lot.

Use mode rb for binary files

I found that the program did not work for me until I changed the file mode on the input from "r" to "rb". This too made it not obvious which direction (base64 to text or vice-versa) was intended. I'd expect the base64 file to be text mode and the binary file to be binary mode.

Allow the user to specify input and output files

The file names are currently hardcoded which certainly greatly restricts the usefulness of the program. Consider using argc and argv to allow the user to specify file names on the command line. Also OUTPUT.base64 is certainly a poor choice for a hardcoded file name since it is actually the input that is encoded in base64.

Avoid fprintf and friends for unformatted output

The code currently uses fprintf with a format string of "%c" for output of decoded byte values. This isn't particularly efficient. fputc would likely be a better choice for this. Better still would likely be to decode multiple bytes and emit them all at the same time using fwrite.

Think carefully about where to put static data

In this code, the base64 static string is in the endecode() function, but it's only used within the enblock() function! Wouldn't it make more sense to put it where it's being used and not have to pass the pointer?

Avoid magic numbers

One of the lines of code here is this:

fprintf(outputFILE, "%c", base64[((in[0] & 3) << 4) | ((in[1] & 240) >> 4)]);

All of those numbers, such as 240 and 4 no doubt mean something, but what? I'm sure I could figure out that this is intended to mask and shift bits into place, but why make it a mystery?

Consider using buffering

In a program like this the typical bottleneck is I/O. For that reason, one strategy for speeding up the program is to use memory (which is fast) in preference to disk I/O (which is triggered by such calls as printf and fread. One way to apply that strategy in this case would be to read a larger chunk into memory, process it there and then write a large chunk of memory to disk at a time. However, it's also often true that disk I/O is already buffered by the underlying operating system so the only way to find out for sure is to measure.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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