1

I've been working on a project where I'm implementing an HTTP server in C, and I'm encountering an issue with rendering images in a web browser. Every time I try to access the image via the server, I receive the error message "the image cannot be displayed because it contains an error."

I've checked my code thoroughly, but I'm unable to pinpoint the exact problem. Could someone please guide me on what I might be doing wrong? Below is a simplified version of my code for serving images:

main.c

#include <stdio.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>

#define PORT "8080"
#define BUFFSIZE 2048

#include "filehandle.h"
#include "httpresponse.h"

char *get_file_path(const char *request) {
    char *path = NULL;
    char *start = strstr(request, "GET ");
    if (start != NULL) {
        start += 4; // Move past "GET "
        char *end = strstr(start, " ");
        if (end != NULL) {
            int path_length = end - start;
            path = (char *)malloc(path_length + 1);
            strncpy(path, start, path_length);
            path[path_length] = '\0';
        }
    }
    return path;
};



char* requestpath(int sockfd)
{
    char *buffer=(char *)malloc(BUFFSIZE);

    int bytes = read(sockfd,buffer,BUFFSIZE);
    return buffer;
};

int main(int argc, char *argv[])
{

    int status;
    struct addrinfo hints, *res;
    int sockfd;
    int bindstatus;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;

    status = getaddrinfo(NULL, PORT, &hints, &res);
    if (status == -1)
    {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
    }

    sockfd = socket(res->ai_family, res->ai_socktype, 0);
    if (sockfd == -1)
    {
        fprintf(stderr, "socket error %s\n", strerror(sockfd));
    }

    int opt = 1;
    setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,(char *)&opt,sizeof(opt));

    bindstatus = bind(sockfd, res->ai_addr, res->ai_addrlen);
    if (bindstatus == -1)
    {
        fprintf(stderr, "bind error %s\n", strerror(bindstatus));
    }

    freeaddrinfo(res);

    int l;
    l = listen(sockfd, 10);
    if (l != 0)
    {
        fprintf(stderr, "LISTEN ERROR %s\n", strerror(l));
    }

    while (1)
    {
        struct sockaddr_in client_addr;
        int newfd;
        socklen_t theiraddr = sizeof client_addr;
        newfd = accept(sockfd,(struct sockaddr *)&client_addr, &theiraddr);


        if (newfd == -1)
        {
            fprintf(stderr, "accept error %s\n", strerror(errno));
        }
        else{
            char str[INET_ADDRSTRLEN];
            inet_ntop(client_addr.sin_family,(struct inaddr *)&client_addr.sin_addr,str,INET_ADDRSTRLEN);
            printf("connection from %s\n",str);
        }

        char *request = requestpath(newfd);
        char *filepath = get_file_path(request);
        printf("%s\n",filepath);
        free(request);

        char *response = NULL;
        if(strcmp(filepath,"/") == 0 || strcmp(filepath,"/index.html") == 0){
            response = content("index.html");

        }
//      else if(strcmp(filepath,"/image.jpg") == 0){
//          FILE *fp = fopen("image.jpg","rb");
//          int count = count_file_bytes(fp);
//          char *content_two = (char*)malloc(sizeof(char)*count+1);

//      }
        else if(strcmp(filepath,"/image.jpg") == 0){
            response = read_binary_file("image.jpg");
        }
        else
            response = "404 Not Found";

        printf("Response is %s\n",response);
        int len, bytes_sent;
        char *http_response = (char *) malloc(4000);
        char *generated_http_response = generate_http_response();
        char *date = get_date_for_server();
        char *ext = get_file_ext(filepath,'.');
        printf("filepath %s\n",filepath);
        char *type = NULL;
        if(ext != NULL){
        type = get_type(ext);

        }
        else{
         type = "text/html";
        }

        int n = snprintf(http_response,4000,generated_http_response,date,type,response);


        len = strlen(http_response);
        bytes_sent = send(newfd, http_response, len, 0);
        free(date);
        shutdown(newfd,SHUT_WR);
    }
    close(sockfd);
    return 0;
}

filehandle.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    // open the requested file and return the file pointer
    FILE* requestfile(char *filename){
        FILE *fp;
        fp = fopen(filename,"r");
        if(fp != NULL){
            return fp;
        }
        else{
            perror("Error opening file\n");
        }
    }
    
    // count the byte size of file
    int count_file_bytes(FILE *fp)
    {
        int c,count = 0;
        if(fp != NULL){
            while((c = fgetc(fp)) != EOF)
                count++;
        }
        fclose(fp);
        return count;
    }
    
    // read the file content to buffer and return
    char* content(char *filename)
    {
        FILE *fp = requestfile(filename);
        int bytecount = count_file_bytes(fp);
        char *content = (char *) malloc(bytecount+1);
    
        FILE *file = requestfile(filename);
        size_t i;
    
        for(i=0;i<(bytecount+1);++i)
        {
            int c = fgetc(file);
            if( c == EOF)
            {
                break;
            }
    
            content[i]= c;
        }
    
        content[i] = '\0';
        fclose(file);
        return content;
    
    
    
    }
    
    // get file extension before last '.'
    char *get_file_ext(char *filename, int c)
    {
        char *type = strrchr(filename,c);
        return type;
    }
    
    
    // read the file in binary format
    char *read_binary_file(char *filename)
    {
        FILE *fp = fopen(filename,"rb");
        if(fp == NULL)
            fprintf(stderr,"\t Error Opening file: %s\n",filename);
        long image_size;
        int c;
        int count = 0;
        fseek(fp,0,SEEK_END);
        image_size = ftell(fp);
        rewind(fp);
        char *buffer = (char *)malloc(image_size);
    
        fread(buffer,1,image_size,fp);
    
        fclose(fp);
        return buffer;
    }


httpresponse.c

#include <time.h>
#include <stdlib.h>

#include <string.h>
#include <stdio.h>

#define MAXSIZE 30

// get the date for http response
char* get_date_for_server()
{
    time_t result = time(NULL);
    char *date = (char *)malloc(MAXSIZE*sizeof(char));
    if(result != (time_t)(-1)){

        size_t t = strftime(date,MAXSIZE,"%a, %d %b %Y %T GMT",localtime(&result));
    }
    return date;

}

// generate http response 
char *generate_http_response()
{
    char *Header =
    "HTTP/1.1 200 OK\r\n"
    "Access-Control-Allow-Origin: *\r\n"
    "Connection: Keep-Alive\r\n"
    "Date: %s\r\n"
    "Content-Type: %s\r\n"
//  "Content-Length: %d\r\n"
    "\n"
    "%s";

    return Header;
}

// return the file mime type 
char *get_type(char *filepath)
{

    char *type = NULL;
    if(strcmp(filepath, ".jpg") == 0){
        type = "image/jpg";
    }
    else {
        type = "text/html";
    }
    return type;
}

here is the output of the browser

output

2
  • 5
    A jpeg file typically has some NUL (=0x00) bytes in it. If you determine its length by strlen (see len = strlen(http_response);), you are not going to get all of it. Same problem with sprintf %s. Commented May 30, 2024 at 18:58
  • 2
    Basically, the problem is that you're treating the JPEG file as a text string. It's binary data, not text. You got the file size in image_size, use that. Commented May 30, 2024 at 19:43

1 Answer 1

0

The issue is the way you are handling the files you're serving. When you use char * you have a string, and strings are intended to be NULL terminated, i.e., if you have the string "Hello", you have the array of bytes { 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00 }. This is the literal equivalent to the "Hello" expression, in a sense that:

char *my_string = "Hello";

and

char *my_string = { 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00 };

Produce the exact same results.

So, this NULL terminator you see (the 0x00 in the end) is expected by all libc string functions. In your case, you user snprintf() for printing the file, literally, as a string (when you user the %s format specifier you're telling the function it's a string). Well, strings are NULL-terminated, they end, to the eyes of libc, when it finds a 0x00. So imagine your file contents were something like this:

char *my_data = {0x10, 0x20, 0x30, 0x00, 0x40, 0x50, 0x60};

A libc string function will read this sequence of bytes only until the 4th byte, and thing it's finished, ignoring the other elements. For instance, if you do a strlen on my_data it will return 3, even though you created a 7 items array.

This is why when you are working with binary data you have to keep track of the size of your data. And obviously, you cannot use string functions on binary data, that will not work! So, stuff like formatting data as %s cannot be done with binary data. They are not meant to be used with any data that doesn't follow string conventions.

To solve your problem, stop using string functions and keep track of data size. I'll provide you with a small example of how to do that so you can adapt to your code needs.

#include <inttypes.h> //for SCNx8
#include <stdio.h>
#include <stdlib.h> //for malloc
#include <string.h> //for memcpy
#include <strings.h> //for bzero

#define BUFFSIZE 2048

struct file_data {
        uint8_t *data;
        size_t length;
};

struct file_data *read_file(const char *path);

int main(int argc, char **argv, char **envp)
{
        struct file_data *file = NULL;
        uint8_t byte = 0x00;
        int position;

        if (argc != 2) {
                fprintf(stderr, "Usage: %s <file-path>\n", argv[0]);
                return -1;
        }

        file = read_file(argv[1]);

        if (file == NULL) {
                perror("Error reading file");
                return -1;
        }

        printf("Content-length: %zu\n", file->length);
        // Dumps file contents in hexadeximal
        while (position < file->length) {
                byte = file->data[position++];
                printf("[0x%02" SCNx8 "] ", byte);
                if (position % 16 == 0)
                        printf("\n");
        }

        free(file->data);
        free(file);

        return 0;
}

struct file_data *read_file(const char *path)
{
        FILE *handler = NULL;
        struct file_data *fdata = NULL;
        size_t read_length = 0;
        uint8_t buffer[BUFFSIZE] = {0x00};

        fdata = malloc(sizeof(struct file_data));
        if (fdata == NULL)
                return NULL; //failed to allocate

        bzero(fdata, sizeof(struct file_data));

        fdata->data = malloc(1);
        if (fdata->data == NULL) { //allocation failed
                free(fdata);
                return NULL;
        }

        handler = fopen(path, "r");
        if (handler == NULL) { //failed to open
                free(fdata->data);
                free(fdata);
                return NULL;
        }

        while (!feof(handler)) {
                read_length = fread(buffer, 1, BUFFSIZE, handler);
                fdata->data = realloc(fdata->data, fdata->length + read_length);
                if (fdata->data == NULL) { //failed to reallocate
                        fclose(handler);
                        free(fdata);
                        return NULL;
                }
                memcpy(fdata->data + fdata->length, buffer, read_length);
                fdata->length += read_length;
        }

        fclose(handler);

        return fdata;
}

As you can see in this hexample (that works similar to an hexdump of sorts), we are treating the file data as binary, the way you should do. I used arrays of uint8_t type, not very usual (although perfectly fine). I made this choice so you don't mistake it for a string, like an unsigned char array could lead you to do.

This code is a quick example ad it's suboptimal, it could easily be improved in terms of performance. But serves as an example of a functioning approach for handling binary contents.

Of course, your use os string functions on HTTP request/response is fine since it's a text protocol, as far as I remember you can always expect the HTTP request to be ASCII text, even when you receive a file upload for in that case, the file will be base64 encoded. But for files that you read on the box running the code, from the filesystem, you better always assume it might be non-ASCII, even if it's, i.e., a text file (the file might be, i.e., corrupted).

Sign up to request clarification or add additional context in comments.

2 Comments

if i want to send the image to browser how can i do that?.
@devMe once you have a socket fd, a pointer to your data and the size, you have everything you need to send it through some system call like write(), send(). In your original code, what you can do is to remove the %s line from the Header variable in generate_http_response. Then after you send() the http_response you send your buffer containing the file contents on a second call to send(). It's one of infinite possible ways, the easiest with your current code. Although your code has A LOOOOT of room for improvement/optimization.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.