From d7b6efe0581b8db3a3d71d8a282ce943c1423a16 Mon Sep 17 00:00:00 2001 From: Timo Ley Date: Wed, 22 Jun 2022 15:00:15 +0200 Subject: [PATCH] Init --- .gitignore | 9 +++ CMakeLists.txt | 6 ++ httplib.c | 103 ++++++++++++++++++++++++++ httplib.h | 109 ++++++++++++++++++++++++++++ httplib_internal.h | 42 +++++++++++ httpparse.c | 168 +++++++++++++++++++++++++++++++++++++++++++ httpser.c | 75 +++++++++++++++++++ httputil.c | 175 +++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 687 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 httplib.c create mode 100644 httplib.h create mode 100644 httplib_internal.h create mode 100644 httpparse.c create mode 100644 httpser.c create mode 100644 httputil.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ee8fe7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +* +!.gitignore +!CMakeLists.txt +!httplib.h +!httplib_internal.h +!httplib.c +!httputil.c +!httpser.c +!httpparse.c \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..29d09c3 --- /dev/null +++ b/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/httplib.c b/httplib.c new file mode 100644 index 0000000..0f79bab --- /dev/null +++ b/httplib.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#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); +} + diff --git a/httplib.h b/httplib.h new file mode 100644 index 0000000..8d55d75 --- /dev/null +++ b/httplib.h @@ -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 \ No newline at end of file diff --git a/httplib_internal.h b/httplib_internal.h new file mode 100644 index 0000000..c333d7f --- /dev/null +++ b/httplib_internal.h @@ -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 diff --git a/httpparse.c b/httpparse.c new file mode 100644 index 0000000..df6c978 --- /dev/null +++ b/httpparse.c @@ -0,0 +1,168 @@ +#include +#include +#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; +} \ No newline at end of file diff --git a/httpser.c b/httpser.c new file mode 100644 index 0000000..d83abc7 --- /dev/null +++ b/httpser.c @@ -0,0 +1,75 @@ +#include +#include +#include +#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; +} \ No newline at end of file diff --git a/httputil.c b/httputil.c new file mode 100644 index 0000000..59f4cd2 --- /dev/null +++ b/httputil.c @@ -0,0 +1,175 @@ +#include +#include +#include "httplib.h" +#include "httplib_internal.h" +#include + +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); +} \ No newline at end of file