This commit is contained in:
Timo Ley 2022-06-22 15:00:15 +02:00
commit d7b6efe058
8 changed files with 687 additions and 0 deletions

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
*
!.gitignore
!CMakeLists.txt
!httplib.h
!httplib_internal.h
!httplib.c
!httputil.c
!httpser.c
!httpparse.c

6
CMakeLists.txt Normal file
View file

@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.10.2)
project(simplehttplib C)
set(CMAKE_C_STANDARD 99)
add_library(simplehttplib STATIC httplib.c httputil.c httpparse.c httpser.c httplib.h httplib_internal.h)

103
httplib.c Normal file
View file

@ -0,0 +1,103 @@
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include "httplib.h"
#include "httplib_internal.h"
int rfd;
int start_http_server(int port, http_request_handler handler) {
rfd = socket(AF_INET, SOCK_STREAM, 0);
if (rfd < 0) {
return SOCKET_ERROR;
}
int option = 1;
setsockopt(rfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(int));
struct sockaddr_in server;
struct sockaddr client;
socklen_t client_len = sizeof(struct sockaddr);
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(port);
if (bind(rfd, (const struct sockaddr *) &server, sizeof(server)) < 0) {
return BIND_ERROR;
}
if (listen(rfd, 3) < 0) {
return LISTEN_ERROR;
}
int cfd;
while (1) {
cfd = accept(rfd, &client, &client_len);
if (cfd < 0) {
printf("Connection Error, waiting for next connection\n");
continue;
}
int pid = fork();
if (pid == 0) {
http_request * req = new_request();
int result = receive(cfd, req);
http_response * res = new_response();
if (result == 1) handler(req, res); //TODO HTTP unsupported version response
else if (result == 4) set_status(res, 400, "Bad Request");
else set_status(res, 500, "Internal Server Error");
free_request(req);
respond(cfd, res);
close(cfd);
exit(0);
}
close(cfd);
}
}
int receive(int cfd, http_request * req_out) {
char buf[1024];
size_t bytes_received;
enum req_pos pos = METHOD;
int was_cr = 0;
int result = 0;
struct tmp tmp;
tmp.buf = malloc(1024);
bzero(tmp.buf, 1024);
tmp.size = 1024;
tmp.pos = 0;
do {
bytes_received = read(cfd, buf, 1024);
if ((result = parse_buffer(buf, bytes_received, req_out, &tmp, &pos, &was_cr))) break;
} while (bytes_received > 0);
free(tmp.buf);
return result;
}
void respond(int cfd, http_response * res) {
size_t res_size = response_size(res);
char * res_bytes = malloc(res_size);
int ser_err = serialize_response(res, res_bytes);
if (ser_err != 0) {
http_response * err_res = new_response();
set_status(res, 500, "Internal Server Error");
res_bytes = realloc(res_bytes, response_size(err_res));
serialize_response(err_res, res_bytes);
free(err_res);
}
free_response(res);
write(cfd, res_bytes, res_size);
free(res_bytes);
}

109
httplib.h Normal file
View file

@ -0,0 +1,109 @@
#ifndef SIMPLEHTTPLIB_HTTPLIB_H
#define SIMPLEHTTPLIB_HTTPLIB_H
/**
* Represents a HTTP request method.
*/
typedef enum http_method_enum {
GET, POST, PUT, DELETE, PATCH, HEAD, CONNECT, OPTIONS, TRACE
} http_method;
/**
* Represents a HTTP header. Contains the header field key, the value and a pointer
* to the next header to form a linked list.
* All pointers of this struct must point to allocated memory or 0.
*/
typedef struct http_header_struct {
char * key;
char * value;
struct http_header_struct * next;
} http_header;
/**
* Represents a HTTP request. Contains the request method, the HTTP path, a linked list
* of headers, the query string (optional, without leading '?'), the request body (optional)
* and the content length of the body (0 if there is no body).
* All pointers of this struct must point to allocated memory or 0.
*/
typedef struct http_request_struct {
http_method method;
char * path;
http_header * headers;
char * query;
size_t content_length;
char * body;
} http_request;
/**
* Represents a HTTP response. Contains the HTTP status code, the status message,
* a linked list of headers, the response body (optional) and the content length
* of the body (0 if there is no body).
* All pointers of this struct must point to allocated memory or 0.
*/
typedef struct http_response_struct {
int status_code;
char * status_message;
http_header * headers;
size_t content_length;
char * body;
} http_response;
/**
* Function pointer type for the HTTP request handler function.
*/
typedef void (*http_request_handler) (http_request * req, http_response * res_out);
/**
* Used to start the HTTP server.
* @param port the port on which the server should listen
* @param handler a function pointer to the request handler function
* @return a negative integer, if the server exited with an error
*/
int start_http_server(int port, http_request_handler handler);
/**
* Helper function to add a header to a HTTP response.
* @param self a pointer to the response struct
* @param key the header field key
* @param value the header value
* @return 0 if the header was set, a negative value if the header was not valid
*/
int add_header(http_response * self, char * key, char * value);
/**
* Helper function to set the content length and to allocate the memory
* for the body of a response struct
* @param self a pointer to the response struct
* @param length the content length
*/
void set_content_length(http_response * self, size_t length);
/**
* Helper function to set the response status of a response struct
* @param self a pointer to the response struct
* @param code the status code
* @param message the status message
*/
void set_status(http_response * self, int code, char * message);
/**
* Helper function to get a header value from a HTTP.
* Returned value is allocated memory and needs to be freed, if not null.
* @param self a pointer to the request struct
* @param key the header field key
* @return the value of the header or null, if the header does not exist in the request
*/
char * get_header(http_request * self, char * key);
/**
* Helper function to get a query value from a HTTP request.
* Returned value is allocated memory and needs to be freed, if not null.
* @param self a pointer to the request struct
* @param key the query key
* @return the value of the query or null, if the query key does not exist in the request
*/
char * get_query_value(http_request * self, char * key);
/**
* Helper function to check if a header is valid.
* @param key the header field key
* @param value the header value
* @return true if the header is valid, otherwise false
*/
int is_valid_header(const char * key, const char * value);
#define SOCKET_ERROR -1;
#define BIND_ERROR -2;
#define LISTEN_ERROR -3;
#endif //SIMPLEHTTPLIB_HTTPLIB_H

42
httplib_internal.h Normal file
View file

@ -0,0 +1,42 @@
#ifndef SIMPLEHTTPLIB_HTTPLIB_INTERNAL_H
#define SIMPLEHTTPLIB_HTTPLIB_INTERNAL_H
struct tmp {
char * buf;
size_t size;
size_t pos;
};
enum req_pos {
METHOD,
PATH,
QUERY,
VERSION,
HEADERS,
BODY
};
void free_request(http_request * self);
void free_response(http_response * self);
http_header * new_header(char * key, char * value);
http_request * new_request();
http_response * new_response();
size_t header_string_size(http_header * header);
int header_to_string(http_header * header, char * str_out);
size_t response_size(http_response * res);
int serialize_response(http_response * res, char * str_out);
int parse_buffer(char * buf, size_t bytes, http_request * req, struct tmp * tmp, enum req_pos * pos, int * was_cr);
int parse_method(char * method_str, http_method * method_out);
int parse_header(http_request * self, char * str);
void increase_tmp(struct tmp * tmp);
void reset_tmp(struct tmp * tmp);
int receive(int cfd, http_request * req_out);
void respond(int cfd, http_response * res);
#endif //SIMPLEHTTPLIB_HTTPLIB_INTERNAL_H

168
httpparse.c Normal file
View file

@ -0,0 +1,168 @@
#include <string.h>
#include <stdlib.h>
#include "httplib.h"
#include "httplib_internal.h"
int parse_buffer(char * buf, size_t bytes, http_request * req, struct tmp * tmp, enum req_pos * pos, int * was_cr) {
for (int i = 0; i < bytes; i++) {
if (buf[i] == '\r') {
*was_cr = 1;
continue;
}
if (*pos == METHOD && buf[i] == ' ') {
int res = parse_method(tmp->buf, &req->method);
if (res < 0) {
return 4;
} else {
*pos = PATH;
reset_tmp(tmp);
}
} else if (*pos == QUERY && buf[i] == ' ') {
req->query = malloc(strlen(tmp->buf) + 1);
strcpy(req->query, tmp->buf);
*pos = VERSION;
reset_tmp(tmp);
//TODO check if valid
} else if (*pos == PATH && buf[i] == ' ') {
*pos = VERSION;
req->path = malloc(strlen(tmp->buf) + 1);
strcpy(req->path, tmp->buf);
reset_tmp(tmp);
} else if (*pos == VERSION && *was_cr && buf[i] == '\n') {
*pos = HEADERS;
if (strcmp(tmp->buf, "HTTP/1.1") != 0) {
return 3;
}
reset_tmp(tmp);
} else if (*pos == PATH && buf[i] == '?') {
*pos = QUERY;
req->path = malloc(strlen(tmp->buf) + 1);
strcpy(req->path, tmp->buf);
reset_tmp(tmp);
} else if (*pos == HEADERS && *was_cr && buf[i] == '\n') {
int res = parse_header(req, tmp->buf);
if (res < 0) {
return 4;
} else if (res > 0) {
*pos = BODY;
if (req->content_length > 0) {
req->body = malloc(req->content_length);
}
}
reset_tmp(tmp);
} else if (*pos != BODY) {
if (tmp->pos == tmp->size - 1) {
increase_tmp(tmp);
}
tmp->buf[tmp->pos] = buf[i];
tmp->pos++;
} else if (req->content_length > tmp->pos) {
req->body[tmp->pos] = buf[i];
tmp->pos++;
} else {
//END OF BODY
return 1;
}
*was_cr = 0;
}
if (*pos == BODY && req->content_length <= tmp->pos) {
return 1;
}
return 0;
}
int parse_method(char * method_str, http_method * method_out) {
if (!strcmp(method_str, "GET")) {
*method_out = GET;
} else if (!strcmp(method_str, "POST")) {
*method_out = POST;
} else if (!strcmp(method_str, "PUT")) {
*method_out = PUT;
} else if (!strcmp(method_str, "DELETE")) {
*method_out = DELETE;
} else if (!strcmp(method_str, "PATCH")) {
*method_out = PATCH;
} else if (!strcmp(method_str, "HEAD")) {
*method_out = HEAD;
} else if (!strcmp(method_str, "CONNECT")) {
*method_out = CONNECT;
} else if (!strcmp(method_str, "OPTIONS")) {
*method_out = OPTIONS;
} else if (!strcmp(method_str, "TRACE")) {
*method_out = TRACE;
} else {
return -1;
}
return 0;
}
int parse_header(http_request * self, char * str) {
size_t len = strlen(str);
if (len == 0) {
return 1;
}
char * key = malloc(len);
bzero(key, len);
char * value = malloc(len);
bzero(value, len);
int key_end = 0;
int value_start = 0;
char c;
int i = 0;
int pos = 0;
while ((c = str[i]) != '\0') {
if (!key_end) {
if (c == ':') {
key_end = 1;
pos = 0;
} else {
key[pos] = c;
pos++;
}
} else if (!value_start && c != ' ') {
value_start = 1;
}
if (value_start) {
value[pos] = c;
pos++;
}
i++;
}
for (int j = pos - 1; j >= 0 ; j--) {
if (value[j] == ' ') {
value[j] = '\0';
} else {
break;
}
}
if (strcasecmp("Content-Length", key) == 0) {
self->content_length = strtoul(value, 0, 10);
return 0;
} else if (is_valid_header(key, value)) {
if (self->headers == 0) {
self->headers = new_header(key, value);
} else {
http_header * run = self->headers;
while (run != 0) {
if (run->next == 0) {
run->next = new_header(key, value);
break;
}
run = run->next;
}
}
free(key);
free(value);
return 0;
}
free(key);
free(value);
return -1;
}

75
httpser.c Normal file
View file

@ -0,0 +1,75 @@
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "httplib.h"
#include "httplib_internal.h"
int header_to_string(http_header * header, char * str_out) {
if (!is_valid_header(header->key, header->value))
return -1;
strcpy(str_out, header->key);
strcat(str_out, ":");
strcat(str_out, header->value);
return 0;
}
size_t header_string_size(http_header * header) {
size_t key_len = strlen(header->key);
size_t value_len = strlen(header->value);
return key_len + value_len + 2;
}
int serialize_response(http_response * res, char * str_out) {
strcpy(str_out, "HTTP/1.1 ");
if (res->status_code < 100 || res->status_code > 599) return -1;
char status[4];
sprintf(status, "%i", res->status_code);
strcat(str_out, status);
strcat(str_out, " ");
strcat(str_out, res->status_message);
strcat(str_out, "\r\n");
http_header * run = res->headers;
while (run != 0) {
char * tmp = malloc(header_string_size(run));
int err = header_to_string(run, tmp);
if (err != 0) return err;
strcat(str_out, tmp);
free(tmp);
strcat(str_out, "\r\n");
run = run->next;
}
if (res->content_length > 0) {
strcat(str_out, "Content-Length:");
char * tmp = malloc(2 + (res->content_length / 10));
sprintf(tmp, "%lu", res->content_length);
strcat(str_out, tmp);
free(tmp);
strcat(str_out, "\r\n");
}
strcat(str_out, "\r\n");
size_t end_pos = 0;
while (str_out[end_pos] != '\0') end_pos++;
if (res->body != 0) {
for (size_t i = 0; i < res->content_length; i++) {
str_out[end_pos + i] = res->body[i];
}
}
return 0;
}
size_t response_size(http_response * res) {
size_t res_line = 15 + strlen(res->status_message); //"HTTP/1.1" 000 (message)\r\n
size_t headers = 2; // \r\n
http_header * run = res->headers;
while (run != 0) {
headers += header_string_size(run) + 1; // \r\n instead of \0
run = run->next;
}
if (res->content_length > 0) {
headers += 18;
headers += res->content_length / 10;
}
size_t body = res->content_length;
return res_line + headers + body;
}

175
httputil.c Normal file
View file

@ -0,0 +1,175 @@
#include <string.h>
#include <stdlib.h>
#include "httplib.h"
#include "httplib_internal.h"
#include <stdio.h>
http_header * new_header(char * key, char * value) {
http_header * header = malloc(sizeof(http_header));
header->next = 0;
header->key = malloc(strlen(key)+1);
header->value = malloc(strlen(value)+1);
strcpy(header->key, key);
strcpy(header->value, value);
return header;
}
http_request * new_request() {
http_request * req = malloc(sizeof(http_request));
req->method = GET;
req->headers = 0;
req->path = 0;
req->query = 0;
req->content_length = 0;
req->body = 0;
return req;
}
http_response * new_response() {
http_response * res = malloc(sizeof(http_request));
res->status_message = 0;
res->headers = 0;
res->body = 0;
res->content_length = 0;
res->status_code = 100;
return res;
}
int add_header(http_response * self, char * key, char * value) {
if (!is_valid_header(key, value)) return -1;
if (self->headers == 0) {
self->headers = new_header(key, value);
} else {
http_header * run = self->headers;
while (run != 0) {
if (run->next == 0) {
run->next = new_header(key, value);
break;
}
run = run->next;
}
}
return 0;
}
char * get_header(http_request * self, char * key) {
http_header * run = self->headers;
while (run != 0) {
if (!strcasecmp(key, run->key)) {
char * value = malloc(strlen(run->value)+1);
strcpy(value, run->value);
return value;
}
run = run->next;
}
return NULL;
}
int is_valid_header(const char * key, const char * value) {
for (int i = 0; key[i] != '\0'; i++) {
char c = key[i];
if ((c < 65 && c != '-') || (c > 90 && c < 97 && c != '_') || c > 122) {
return 0;
}
}
for (int i = 0; value[i] != '\0'; i++) {
char c = value[i];
if (c < 32 || c > 126) {
return 0;
}
}
return 1;
}
void free_request(http_request * self) {
if (self == 0) return;
if (self->headers != 0) {
http_header * run = self->headers;
while (run != 0) {
http_header * to_free = run;
run = run->next;
if (to_free->key != 0) free(to_free->key);
if (to_free->value != 0) free(to_free->value);
free(to_free);
}
}
if (self->query != 0) free(self->query);
if (self->body != 0) free(self->body);
if (self->path != 0) free(self->path);
free(self);
}
void free_response(http_response * self) {
if (self == 0) return;
if (self->headers != 0) {
http_header * run = self->headers;
while (run != 0) {
http_header * to_free = run;
run = run->next;
if (to_free->key != 0) free(to_free->key);
if (to_free->value != 0) free(to_free->value);
free(to_free);
}
}
if (self->status_message != 0) free(self->status_message);
if (self->body != 0) free(self->body);
free(self);
}
char * get_query_value(http_request * self, char * key) {
if (self->query == 0) return NULL;
char * tmp = malloc(strlen(self->query)+1);
int pos = 0;
int value_start = -1;
for (int i = 0; self->query[i] != '\0'; i++) {
if (self->query[i] == '=') {
tmp[pos] = '\0';
if (!strcmp(key, tmp)) {
value_start = i+1;
}
pos = 0;
} else if (self->query[i] == '&') {
if (value_start > 0) {
break;
} else {
pos = 0;
}
} else {
tmp[pos] = self->query[i];
pos++;
}
}
tmp[pos] = '\0';
if (value_start > 0) {
char * value = malloc(strlen(tmp)+1);
strcpy(value, tmp);
free(tmp);
return value;
}
free(tmp);
return NULL;
}
void set_content_length(http_response * self, size_t length) {
self->content_length = length;
if (length > 0)
self->body = malloc(length);
}
void set_status(http_response * self, int code, char * message) {
self->status_code = code;
self->status_message = malloc(strlen(message)+1);
strcpy(self->status_message, message);
}
void increase_tmp(struct tmp * tmp) {
tmp->size += 1024;
tmp->buf = realloc(tmp->buf, tmp->size);
bzero(tmp->buf + tmp->pos, 1024);
}
void reset_tmp(struct tmp * tmp) {
tmp->pos = 0;
bzero(tmp->buf, tmp->size);
}