diff --git a/httpd/Makefile b/httpd/Makefile index 386b778..731ffac 100644 --- a/httpd/Makefile +++ b/httpd/Makefile @@ -7,11 +7,14 @@ CFLAGS_DBG = -g ASAN_DBG_FLAGS = -fsanitize=address UTILS_SRCS = src/utils/string/string.c \ - src/utils/time/fmt_time.c + src/utils/time/fmt_time.c \ + src/utils/parsing/words.c \ + src/utils/parsing/blanks.c MODULES_SRCS = src/config/config.c \ src/server/server.c \ src/http/http.c \ - src/logger/logs.c + src/logger/logs.c \ + src/logger/errors.c SRCS = $(UTILS_SRCS) \ $(MODULES_SRCS) \ src/main.c diff --git a/httpd/src/http/http.c b/httpd/src/http/http.c index 6cb59d0..8b7c707 100644 --- a/httpd/src/http/http.c +++ b/httpd/src/http/http.c @@ -1,10 +1,147 @@ #include "http.h" -struct http_request *parse_request(struct string *req) -{} +#include +#include +#include -struct http_response *generate_response(struct http_request *req) -{} +#include "../utils/parsing/words.h" -struct string *format_response(struct http_response *resp) -{} +// === Static functions + +static void destroy_headers(struct http_header *headers) +{ + while (headers != NULL) + { + struct http_header *next = headers->next; + free(headers); + headers = next; + } +} + +static ssize_t read_field(struct string *str, size_t offset, + struct string **res) +{ + ssize_t nread = read_word_delim(str, offset, res, ":\n"); + if (nread <= 0) + return ERR_HTTP_INVALID_INPUT; + + if (str->size <= offset + nread || str->data[offset + nread] != ':') + return ERR_HTTP_INVALID_INPUT; + + return nread; +} + +static ssize_t read_value(struct string *str, size_t offset, + struct string **res) +{ + ssize_t nread = read_word_delim(str, offset, res, "\n"); + if (nread <= 0) + return ERR_HTTP_INVALID_INPUT; + + if (str->size <= offset + nread || str->data[offset + nread] != '\n') + return ERR_HTTP_INVALID_INPUT; + + return nread; +} + +// Parses the status line of req, stores the result in res and returns the +// number of read characters or a negative number on error +// WARNING res must be pre allocated +// (See the header for error codes) +static ssize_t parse_reqline(struct http_request *res, struct string *req) +{ + ssize_t i = 0; + ssize_t skipped; + + if (res == NULL) + return ERR_HTTP_INTERNAL_ERROR; + + // Method + if (strncmp(req->data, "GET", strlen("GET")) == 0) + { + res->method = GET; + i += strlen("GET"); + } + else if (strncmp(req->data, "HEAD", strlen("HEAD")) == 0) + { + res->method = HEAD; + i += strlen("HEAD"); + } + else + return ERR_HTTP_NOT_IMPLEMENTED; + + // Skip space + if (req->data[i++] != ' ') + return ERR_HTTP_INVALID_INPUT; + + // Target (path) + skipped += read_word(req, i, &res->path); + if (skipped <= 0) + return ERR_HTTP_INVALID_INPUT; + + // Skip space + if (req->data[i++] != ' ') + return ERR_HTTP_INVALID_INPUT; + + // Protocol + skipped += read_word(req, i, &res->protocol); + if (skipped <= 0) + return ERR_HTTP_INVALID_INPUT; + + // EOL + if (req->data[i++] != '\n') + return ERR_HTTP_INVALID_INPUT; + + return i; +} + +static ssize_t parse_headers(struct http_header *res, struct string *req, + size_t offset) +{ + size_t i = offset; + + while (req->data[i] != '\n') // ! Blank line + { + // Read field + ssize_t nread = read_field(req, offset, &res->field); + if (nread <= 0) + return nread; // Contains error code when negative + + i += nread; + + // Read value + nread = read_value(req, offset, &res->value); + if (nread <= 0) + return nread; // Contains error code when negative + + i += nread + 1; + } + + return i + 1; +} + +// === Functions + +// struct http_request *parse_request(struct string *req) +// {} + +// struct http_response *generate_response(struct http_request *req) +// {} + +// struct string *format_response(struct http_response *resp) +// {} + +void destroy_request(struct http_request *req) +{ + if (req != NULL) + { + if (req->path != NULL) + string_destroy(req->path); + if (req->protocol != NULL) + string_destroy(req->protocol); + + destroy_headers(req->headers); + + free(req); + } +} diff --git a/httpd/src/http/http.h b/httpd/src/http/http.h index f52bb72..c1ca4a8 100644 --- a/httpd/src/http/http.h +++ b/httpd/src/http/http.h @@ -1,6 +1,18 @@ #ifndef HTTP_H #define HTTP_H +// === Definitions + +#define _POSIX_C_SOURCE 200809L + +// Error codes +#define ERR_HTTP_INVALID_INPUT -1 +#define ERR_HTTP_NOT_IMPLEMENTED -2 +#define ERR_HTTP_OUT_OF_MEMORY -4 +#define ERR_HTTP_INTERNAL_ERROR -5 + +// === Includes + #include "../utils/string/string.h" // === Enums @@ -8,11 +20,11 @@ enum http_method { GET, - POST, - PUT, - DELETE, + // POST, + // PUT, + // DELETE, // PATCH, - // HEAD, + HEAD, // OPTIONS, // CONNECT, // TRACE @@ -32,14 +44,12 @@ struct http_request enum http_method method; struct string *path; struct string *protocol; - struct string *protocol_version; struct http_header *headers; // Headers linked list }; struct http_response { struct string *protocol; - struct string *protocol_version; int status_code; struct string *status_msg; struct http_header *headers; // Headers linked list @@ -49,7 +59,7 @@ struct http_response /* @brief Parses the HTTP request and splits it into a request structure * - * @params req + * @param req * * @return A pointer to the structure containing the request infos on success, * NULL otherwise @@ -58,7 +68,7 @@ struct http_response /* @brief Generates a response to the given request * - * @params req + * @param req * * @return A pointer to the generated response struct on success, * NULL otherwise @@ -68,12 +78,23 @@ struct http_response /* @brief Formats the given response structure into a valid HTTP response * string * - * @params resp + * @param resp * * @return A pointer to the string containing the response on success, * NULL otherwise */ struct string* format_response(struct http_response* resp); +/* @brief Free all allocated memory inside req and req itself + * + * @param req + */ +void destroy_request(struct http_request *req); + +/* @brief Free all allocated memory inside resp and resp itself + * + * @param resp + */ +void destroy_response(struct response *resp); #endif // ! HTTP_H diff --git a/httpd/src/logger/errors.c b/httpd/src/logger/errors.c index 1dfa67e..bca0182 100644 --- a/httpd/src/logger/errors.c +++ b/httpd/src/logger/errors.c @@ -4,6 +4,7 @@ #include #include +#include "../utils/time/fmt_time.h" #include "logs.h" // === Static variable diff --git a/httpd/src/server/server.c b/httpd/src/server/server.c index f2f7b1f..ea193a4 100644 --- a/httpd/src/server/server.c +++ b/httpd/src/server/server.c @@ -9,7 +9,7 @@ #include #include "../logger/errors.h" -#include "../logger/logs.h" +// #include "../logger/logs.h" // === Definitions @@ -39,7 +39,7 @@ static int get_socket(const char *hostname, const char *port) int sock_fd; bool socket_bound = false; struct addrinfo *cur_addr = client_addr; - while (cur_addr != NULL && socket_bound) + while (cur_addr != NULL && !socket_bound) { // Create socket sock_fd = socket(cur_addr->ai_family, cur_addr->ai_socktype, diff --git a/httpd/src/utils/parsing/blanks.c b/httpd/src/utils/parsing/blanks.c new file mode 100644 index 0000000..ac7c20f --- /dev/null +++ b/httpd/src/utils/parsing/blanks.c @@ -0,0 +1,46 @@ +#include "blanks.h" + +bool is_space(char c) +{ + switch (c) + { + case ' ': + case '\t': + return true; + + default: + return false; + } +} + +bool is_blank(char c) +{ + switch (c) + { + case '\f': + case '\n': + case '\r': + case '\t': + case '\v': + case ' ': + return true; + + default: + return false; + } +} + +ssize_t skip_blanks(struct string *str, size_t offset) +{ + size_t i = offset; + while (i < str->size) + { + if (!is_space(str->data[i])) + return i; + + i++; + } + + // Reached EOL + return i; +} diff --git a/httpd/src/utils/parsing/blanks.h b/httpd/src/utils/parsing/blanks.h new file mode 100644 index 0000000..68c9778 --- /dev/null +++ b/httpd/src/utils/parsing/blanks.h @@ -0,0 +1,38 @@ +#ifndef BLANKS_H +#define BLANKS_H + +// === Definitions + +#define BLANKS_LIST "\f\n\r\t\v " + +// === Includes + +#include +#include +#include +#include "../string/string.h" + +// === Functions +/* + * NOTE: + * As ispace(3) and isblank(3) functions from stdlib can be confusing and not + * always best suited for what I want to do, I decided to reimplement them. + * So be warned, they won't have the same behavior as their libc counterparts. + */ + +/* + * Doc: TODO + */ +bool is_space(char c); + +/* + * Doc: TODO + */ +bool is_blank(char c); + +/* + * Doc: TODO + */ +ssize_t skip_blanks(struct string* str, size_t offset); + +#endif // ! BLANKS_H diff --git a/httpd/src/utils/parsing/words.c b/httpd/src/utils/parsing/words.c new file mode 100644 index 0000000..cea5a79 --- /dev/null +++ b/httpd/src/utils/parsing/words.c @@ -0,0 +1,107 @@ +#include "words.h" + +#include "blanks.h" + +bool str_contains(const char *str, char c) +{ + int i = 0; + while (str[i] != '\0') + { + if (str[i] == c) + return true; + + i++; + } + + return false; +} + +ssize_t read_word(struct string *str, size_t offset, struct string **res) +{ + size_t i = offset; + while (i < str->size) + { + if (is_blank(str->data[i])) + break; + + i++; + } + + *res = string_create(str->data + offset, i - offset); + if (res == NULL) + return -1; + + return i - offset; +} + +ssize_t read_word_delim(struct string *str, size_t offset, struct string **res, + const char *delims) +{ + size_t i = offset; + bool found = false; + + while (i < str->size && !found) + { + if (str_contains(delims, str->data[i])) + { + found = true; + break; + } + + i++; + } + + *res = string_create(str->data + offset, i - offset); + if (res == NULL) + return -1; + + return i - offset; +} + +ssize_t read_word_restrict(struct string *str, size_t offset, + struct string **res, const char *restr) +{ + size_t i = offset; + bool found = false; + + while (i < str->size && !found) + { + if (!str_contains(restr, str->data[i])) + { + found = true; + break; + } + + i++; + } + + *res = string_create(str->data + offset, i - offset); + if (res == NULL) + return -1; + + return i - offset; +} + +ssize_t read_word_predicate(struct string *str, size_t offset, + struct string **res, bool (*predicate)(char c)) +{ + size_t i = offset; + bool found = false; + + while (i < str->size && !found) + { + if (predicate(str->data[i])) + { + found = true; + break; + } + + i++; + } + + *res = string_create(str->data + offset, i - offset); + if (res == NULL) + return -1; + + return i - offset; +} diff --git a/httpd/src/utils/parsing/words.h b/httpd/src/utils/parsing/words.h new file mode 100644 index 0000000..265212b --- /dev/null +++ b/httpd/src/utils/parsing/words.h @@ -0,0 +1,42 @@ +#ifndef WORDS_H +#define WORDS_H + + +// === Includes + +#include +#include +#include +#include "../string/string.h" + +// === Functions + +/* + * Doc: TODO + */ +bool str_contains(const char *str, char c); + +/* + * Doc: TODO + */ +ssize_t read_word(struct string* str, size_t offset, struct string** res); + +/* + * Doc: TODO + */ +ssize_t read_word_delim(struct string *str, size_t offset, struct string **res, + const char *delims); + +/* + * Doc: TODO + */ +ssize_t read_word_restrict(struct string *str, size_t offset, struct string **res, + const char *restr); + +/* + * Doc: TODO + */ +ssize_t read_word_predicate(struct string *str, size_t offset, + struct string **res, bool (*predicate)(char c)); + +#endif // ! WORDS_H diff --git a/httpd/src/utils/time/fmt_time.c b/httpd/src/utils/time/fmt_time.c index 8181509..a2590e4 100644 --- a/httpd/src/utils/time/fmt_time.c +++ b/httpd/src/utils/time/fmt_time.c @@ -1,8 +1,8 @@ -#include - #include "fmt_time.h" -static char *get_time(void) +#include + +char *get_time(void) { time_t local_ts = time(NULL); struct tm *gmt_time = gmtime(&local_ts);