5

I try to implement a simple HTTP server using C++. I was able to send text response to browser, but I am failing to send response for binary file request.

Here is my code to get HTML response to PNG file request:

string create_html_output_for_binary(const string &full_path)
{
    const char* file_name = full_path.c_str();

    FILE* file_stream = fopen(file_name, "rb");

    string file_str;

    size_t file_size;

    if(file_stream != nullptr)
    {
        fseek(file_stream, 0, SEEK_END);

        long file_length = ftell(file_stream);

        rewind(file_stream);

        // Allocate memory. Info: http://www.cplusplus.com/reference/cstdio/fread/?kw=fread
        char* buffer = (char*) malloc(sizeof(char) * file_length);

        if(buffer != nullptr)
        {
            file_size = fread(buffer, 1, file_length, file_stream);

            stringstream out;

            for(int i = 0; i < file_size; i++)
            {
                out << buffer[i];
            }

            string copy = out.str();

            file_str = copy;
        }
        else
        {
            printf("buffer is null!");
        }
    }
    else
    {
        printf("file_stream is null! file name -> %s\n", file_name);
    }

    string html = "HTTP/1.1 200 Okay\r\nContent-Type: text/html; charset=ISO-8859-4 \r\n\r\n" + string("FILE NOT FOUND!!");

    if(file_str.length() > 0)
    {
        // HTTP/1.0 200 OK
        // Server: cchttpd/0.1.0
        // Content-Type: image/gif
        // Content-Transfer-Encoding: binary
        // Content-Length: 41758

        string file_size_str = to_string(file_str.length());

        html = "HTTP/1.1 200 Okay\r\nContent-Type: image/png; Content-Transfer-Encoding: binary; Content-Length: " + file_size_str + ";charset=ISO-8859-4 \r\n\r\n" + file_str;

        printf("\n\nHTML -> %s\n\nfile_str -> %ld\n\n\n", html.c_str(), file_str.length());
    }

    return html;
}

This code successfully read file and store data on char* buffer. What makes me confuse is the file_str always contains \211PNG, although when I check its size, is much large than \211PNG. I suspect this is the problem that cause my image does not loaded in browsers because when I printf the html, it only shows:

HTTP/1.1 200 Okay
Content-Type: image/png; Content-Transfer-Encoding: binary; Content-Length: 187542;charset=ISO-8859-4 

\211PNG

What I am thinking is the way to send binary data to browser is same with sending text data, so I make the string header first, then read file data, convert it to string and combine with the header, then finally send a large single string to HTTP socket.

I also tried this code:

if (file_stream != NULL)
{
    short stringlength = 6;

    string mystring;

    mystring.reserve(stringlength);

    fseek(file_stream , 0, SEEK_SET);
    fread(&mystring[0], sizeof(char), (size_t)stringlength, file_stream);

    printf("TEST -> %s, length -> %ld\n", mystring.c_str(), mystring.length());

    fclose(file_stream );
}

But the HTML output always the same, and mystring also contains only \211PNG when printf-ed.

Am I in the wrong path? Please help find out the mistakes in my code. Thank you.

yunhasnawa
  • 815
  • 1
  • 14
  • 30
  • It looks like the PNG file contains a NULL character. printf's '%s' prints a char string until it encounters NULL and stops. Try using C++ streams instead: cout << mystring; – nishantjr May 26 '13 at 17:58
  • You should consider using `std::ifstream` and `std::ostream` rather than the C stream I/O calls. – Captain Obvlious May 26 '13 at 18:23
  • This is almost not a C++ but C. Turning to C++ makes things simpler. As the answer below shows. Hacking your way to the solution is definitely one "wrong path". –  Dec 31 '18 at 11:37
  • There is no need to read the entire file into memory, and certainly no need to convert it from a byte array to any other format. – user207421 Dec 25 '19 at 01:00

2 Answers2

3

You are storing the data in one large chunk into a std::stringstream. That will not work as the value zero will be interpreted as a null terminator. This causes everything after the null terminator to be ignored. You should use a container like std::vector to store and manage binary data.

#include <vector>

string create_html_output_for_binary(const string &full_path)
{
    std::vector<char> buffer;

    //... other code here

    if(ile_stream != nullptr)
    {
        fseek(file_stream, 0, SEEK_END);
        long file_length = ftell(file_stream);
        rewind(file_stream);

        buffer.resize(file_length);

        file_size = fread(&buffer[0], 1, file_length, file_stream);
    }

    // .... other code here
}

To output the data do not use printf. It may handle new lines differently and will stop at the first null terminator is encounters. Instead (keeping with your use of C stream IO) use fwrite.

fwrite (buffer.data(), 1 , buffer.size() , stdout );

In order for the above to work you will need to reopen the stdout to that it writes in binary mode. This answer here on Stackoverflow shows how to accomplish that. This is just to output the contents to the stdout Since you are transmitting the date over sockets you do not have to do anything to stdout.

Community
  • 1
  • 1
Captain Obvlious
  • 19,754
  • 5
  • 44
  • 74
  • 1
    \0 is only treated specially when converting from const char*, and no length is available. – MSalters May 26 '13 at 18:08
  • Ah yes, thank you. I've updated it to remove the reference to `string` and updated the description – Captain Obvlious May 26 '13 at 18:20
  • Umm.. Then how should I convert the data in vector to string? I've tried `string binary_str(buffer.begin(), buffer.end());`, but still, the string is same `\211PNG` :( – yunhasnawa May 26 '13 at 19:07
  • I've updated my answer to include the use of `fwrite` and a link on how to change `stdout` so that it works in binary mode instead of text to prevent potential mangling of newline characters. – Captain Obvlious May 26 '13 at 19:35
0

First, you need to open the file(where your picture is) to read binary.

fp=fopen(filename,"rb");

Next,set stdout to binary mode with this command:

 _setmode(_fileno(stdout),O_BINARY);

You need to include <fcntl.h> and <io.h> headers.Find the exact size of the picture you need to send,for example like Captain Obvilous has written:

  fseek(fp, 0L, SEEK_END);
long file_length = ftell(fp);
rewind(fp);

Now use fread function to read all bytes from the picture:

    while (!feof(fp))
  {
      fread(&ch, 1, 1, fp);
      cout << ch;
  }

Variable ch is type char.When you are finish set file mode back:

_setmode(_fileno(stdout), _O_TEXT);

**NOTE:**This code was written mostly in C but you can easily read files in C++ using istream