I'm trying to POST a binary file to a web server with a client program written in C (Windows). I'm pretty new to socket programming, so tried POST requests using multipart/form-data
with plain text messages, and text-based files (.txt, .html, .xml). Those seem to work fine. But when trying to send a PNG file, I'm running into some problems.
The following is how I read the binary file
FILE *file;
char *fileName = "download.png";
long int fileLength;
//Open file, get its size
file = fopen(fileName, "rb");
fseek(file, 0, SEEK_END);
fileLength = ftell(file);
rewind(file);
//Allocate buffer and read the file
void *fileData = malloc(fileLength);
memset(fileData, 0, fileLength);
int n = fread(fileData, 1, fileLength, file);
fclose(file);
I confirmed that all the bytes are getting read properly.
This is how I form my message header and body
//Prepare message body and header
message_body = malloc((int)1000);
sprintf(message_body, "--myboundary\r\n"
"Content-Type: application/octet-stream\r\n"
"Content-Disposition: form-data; name=\"myFile\"; filename=\"%s\"\r\n\r\n"
"%s\r\n--myboundary--", fileName, fileData);
printf("\nSize of message_body is %d and message_body is \n%s\n", strlen(message_body), message_body);
message_header = malloc((int)1024);
sprintf(message_header, "POST %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Content-Type: multipart/form-data; boundary=myboundary\r\n"
"Content-Length: %d\r\n\r\n", path, host, strlen(message_body));
printf("Size of message_header is %d and message_header is \n%s\n", strlen(message_header), message_header);
The connection and sending part also works fine as the request is received properly. But, the received png file is ill-formatted.
The terminal prints out the following for fileData
if I use %s
in printf
ëPNG
I searched around and came to know that binary data doesn't behave like strings and thus printf/ sprintf/ strcat etc. cannot be used on them. As binary files have embedded null characters, %s
won't print properly. It looks like that is the reason fileData
only printed the PNG header.
Currently, I send two send()
requests to server. One with the header and the other with body and footer combined. That was working for text-based files. To avoid using sprintf
for binary data, I tried sending one request for header, one for binary data (body) & one for footer. That doesn't seem to work either.
Also, found that memcpy
could be used to append binary data to normal string. That didn't work either. Here is how I tried that (Not sure whether my implementation is correct or not).
sprintf(message_body, "--myboundary\r\n"
"Content-Disposition: form-data; name=\"text1\"\r\n\r\n"
"text default\r\n"
"--myboundary\r\n"
"Content-Type: application/octet-stream\r\n"
"Content-Disposition: form-data; name=\"myFile\"; filename=\"%s\"\r\n\r\n", fileName);
char *message_footer = "\r\n--myboundary--";
char *message = (char *)malloc(strlen(message_body) strlen(message_footer) fileLength);
strcat(message, message_body);
memcpy(message, fileData, fileLength);
memcpy(message, message_footer, strlen(message_footer));
I'm stuck at how I could send my payload which requires appending of string (headers), binary data (payload), string (footer).
Any advice/ pointers/ reference links for sending the whole file would be appreciated. Thank You!
CodePudding user response:
How to print binary data
In your question, you stated you were having trouble printing binary data with printf
, due to the binary data containing bytes with the value 0
. Another problem (that you did not mention) is that binary data may contain non-printable characters.
Binary data is commonly represented in one of the following ways:
- In hexadecimal representation
- In textual representation, replacing non-printable characters with placeholer characters
- Both of the above
I suggest that you create your own simple function for printing binary data, which implements option #3. You can use the function isprint
to determine whether a character is printable, and if it isn't, you can place some placeholer character (such as 'X'
) instead.
Here is a small program which does that:
#include <stdio.h>
#include <ctype.h>
#include <string.h>
void print_binary( char *data, size_t length )
{
for ( size_t i = 0; i < length; i = 16 )
{
int bytes_in_line = length - i >= 16 ? 16 : length - i;
//print line in hexadecimal representation
for ( int j = 0; j < 16; j )
{
if ( j < bytes_in_line )
printf( "X ", data[i j] );
else
printf( " " );
}
//add spacing between hexadecimal and textual representation
printf( " " );
//print line in textual representation
for ( int j = 0; j < 16; j )
{
if ( j < bytes_in_line )
{
if ( isprint( (unsigned char)data[i j] ) )
putchar( data[i j] );
else
putchar( 'X' );
}
else
{
putchar( ' ' );
}
}
putchar( '\n' );
}
}
int main( void )
{
char *text = "This is a string with the unprintable backspace character \b.";
print_binary( text, strlen( text ) );
return 0;
}
The output of this program is the following:
54 68 69 73 20 69 73 20 61 20 73 74 72 69 6E 67 This is a string
20 77 69 74 68 20 74 68 65 20 75 6E 70 72 69 6E with the unprin
74 61 62 6C 65 20 62 61 63 6B 73 70 61 63 65 20 table backspace
63 68 61 72 61 63 74 65 72 20 08 2E character X.
As you can see, the function print_binary
printed the data in both hexadecimal representation and textual representation, 16 bytes per line, and it correctly replaced the non-printable backspace character with the placeholer 'X'
character when printing the textual representation.
Wrong printf
conversion format specifier
The line
printf("\nSize of message_body is %d and message_body is \n%s\n", strlen(message_body), message_body);
is wrong. The return type of strlen
is size_t
, not int
. The correct printf
conversion format specifier for size_t
is %zu
, not %d
. Using the wrong format specifier causes undefined behavior, which means that it may work on some platforms, but not on others.
Concatenating string with binary data
The following lines are wrong:
char *message = (char *)malloc(strlen(message_body) strlen(message_footer) fileLength);
strcat(message, message_body);
memcpy(message, fileData, fileLength);
memcpy(message, message_footer, strlen(message_footer));
The function strcat
requires both function arguments to point to null-terminated strings. However, the first function argument is not guaranteed to be null-terminated. I suggest that you use strcpy
instead of strcat
.
Also, in your question, you correctly stated that the file binary data should be appended to the string. However, that is not what the line
memcpy(message, fileData, fileLength);
is doing. It is instead overwriting the string.
In order to append binary data to a string, you should only overwrite the terminating null character of the string, for example like this:
memcpy( message strlen(message), fileData, fileLength );