diff options
-rw-r--r-- | Makefile | 13 | ||||
-rw-r--r-- | http.c | 351 | ||||
-rw-r--r-- | http.h | 40 | ||||
-rw-r--r-- | plop.c | 2 | ||||
-rw-r--r-- | plop.h | 1 | ||||
-rw-r--r-- | request.c | 317 | ||||
-rw-r--r-- | request.h | 38 | ||||
-rw-r--r-- | response.c | 43 | ||||
-rw-r--r-- | response.h | 6 |
9 files changed, 417 insertions, 394 deletions
@@ -3,9 +3,16 @@ CFLAGS+=-I/usr/include/lua5.3 LDLIBS+=-llua5.3 PREFIX?=/usr/local -plop: main.o plop.o http.o -main.o plop.o: plop.h -main.o plop.o http.o: http.h +plop: main.o plop.o http.o response.o request.o + +http.o: http.h +main.o: plop.h +plop.o: http.h plop.h request.h response.h +request.o: http.h request.h +response.o: response.h + +plop.h: http.h request.h +response.h: http.h clean: rm -f plop *.o @@ -1,15 +1,5 @@ #include "http.h" -#include <ctype.h> -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -static const int REQUEST_DATA_SIZE = 4096; -static const int MAX_HEADERS = 30; - const char * status_str[] = { [STATUS_OK] = "200 OK", [STATUS_BAD_REQUEST] = "400 Bad Request", @@ -19,344 +9,3 @@ const char * status_str[] = { [STATUS_NOT_IMPLEMENTED] = "501 Not Implemented", [STATUS_VERSION_NOT_SUPPORTED] = "505 Version Not Supported", }; - -/// Allocates and initializes request structure. -/// \return Pointer to initialized request or NULL in case of an error -/// \see /free_request -struct request * new_request(void) -{ - struct request * request = malloc(sizeof(struct request)); - - if (NULL == request) - { - return NULL; - } - - memset(request, 0, sizeof(struct request)); - request->step = parse_step_method; - - request->data = malloc(REQUEST_DATA_SIZE); - - if (NULL == request->data) - { - free(request); - return NULL; - } - - return request; -} - -/// Releases memory used by the request and the request itself. -/// \param request Request to free -/// \see /new_request -void free_request(struct request * request) -{ - if (NULL != request->data) - { - free(request->data); - } - - if (NULL != request->headerv) - { - free(request->headerv); - } - - free(request); -} - -/// Sends a simple response only with a status to the client. -/// \param fd File descriptor of the client socket -/// \param status HTTP response status code -/// \return Negative value if an error was encountered; numbers of bytes written otherwise -int respond_only_status(const int fd, const enum status status) -{ - static const char * pattern = - "HTTP/1.1 %s\r\n" - "Connection: close\r\n" - "\r\n"; - - return dprintf(fd, pattern, status_str[status]); -} - -/// Sends a response with a status and a body to the client. -/// \param fd File descriptor of the client socket -/// \param status HTTP response status code -/// \param body Content that will be sent -/// \param size Size of the content in bytes -/// \return Negative value if an error was encountered; numbers of bytes written otherwise -int respond_with_body(const int fd, const enum status status, const char * body, const int size) -{ - static const char * pattern = - "HTTP/1.1 %s\r\n" - "Connection: close\r\n" - "Content-Type: application/json\r\n" - "Content-Size: %d\r\n" - "\r\n"; - - if (0 > dprintf(fd, pattern, status_str[status], size)) - { - return -1; // TODO: Handle errors properly - } - - return write(fd, body, size); -} - -/// Collects request between calls to `poll`. -/// \param fd Client socket -/// \param request Pointer to current request context of handled client -/// \return Number of bytes parsed, -1 if an error occured or 0 if expects more data -int parse_request(const int fd, struct request ** request) -{ - if (NULL == *request) - { - *request = new_request(); - if (NULL == *request) - { - return -1; - } - } - - // TODO: Expand buffer until EAGAIN or arbitrary limit - int length = read(fd, (*request)->data, REQUEST_DATA_SIZE - 1); - - if (0 == length || (-1 == length && EWOULDBLOCK != errno && EAGAIN != errno)) - { - return -1; // TODO: Handle errors properly - } - - (*request)->data[length] = 0; - (*request)->length = length; - - return (*request)->step(*request); -} - - -/// Progresses the readout of the request until delimiter is found. -/// \param request Request to process -/// \param delimiter Character that marks the end of the readout -/// \return Position of the delimiter or 0 if the deliminter could not be found -static int read_until_char(struct request * request, const char delimiter) -{ - char character; - while (request->position < request->length) - { - character = request->data[request->position]; - if (delimiter == character) - { - return request->position; - } - request->position++; - } - return 0; -} - -/// Progresses the readout of the request until non-whitespace character is found. -/// \param request Request to process -/// \return Position of the first non-whitespace character or 0 if it could not be found -/// \see isalpha(3) -static int read_until_word(struct request * request) -{ - char character; - while (request->position < request->length) - { - character = request->data[request->position]; - if (!isspace(character)) - { - return request->position; - } - request->position++; - } - return 0; -} - -/// Progresses the readout of the request until line separator is found. -/// \param request Request to process -/// \return Position of the first byte of the separator or 0 if line separator could not be found -static int read_rest_of_line(struct request * request) -{ - while (request->position < request->length - 1) - { - if ('\r' == request->data[request->position] && '\n' == request->data[request->position + 1]) - { - return request->position; - } - else if ('\r' == request->data[request->position + 1]) - { - request->position += 1; - } - else - { - request->position += 2; - } - } - return 0; -} - -/// Parses method field of the request. -/// \param request Request to process -/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed -int parse_step_method(struct request * request) -{ - request->method.length = read_until_char(request, ' '); - - if (0 == request->method.length) - { - return 0; - } - - request->step = parse_step_path; - - return parse_step_path(request); -} - -/// Parses path field of the request. -/// \param request Request to process -/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed -// TODO: Consider spliting path into an actual path and arguments in this stage -int parse_step_path(struct request * request) -{ - if (0 >= request->path.start) - { - request->path.start = read_until_word(request); - - if (0 == request->path.start) - { - return 0; - } - } - - const int result = read_until_char(request, ' '); - - if (0 == result) - { - return 0; - } - - request->path.length = request->position - request->path.start; - request->step = parse_step_version; - - return parse_step_version(request); -} - -/// Parses and verifies http version field of the request. -/// \param request Request to process -/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed -// TODO: Return -1 if version is unsupported, meaning other than HTTP/1.1 -int parse_step_version(struct request * request) -{ - if (0 >= request->version.start) - { - request->version.start = read_until_word(request); - - if (0 == request->version.start) - { - return 0; - } - } - - const int result = read_rest_of_line(request); - - if (0 == result) - { - return 0; - } - - request->version.length = request->position - request->version.start; - request->step = parse_step_header_name; - - return parse_step_header_name(request); -} - -/// Parses and verifies name of a single header from the request. -/// \param request Request to process -/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed -int parse_step_header_name(struct request * request) -{ - if (NULL == request->headerv) - { - request->headerv = malloc(sizeof(struct header) * MAX_HEADERS); - request->headerc = 0; - memset(request->headerv, 0, sizeof(struct header) * MAX_HEADERS); - - if (NULL == request->headerv) - { - return -1; - } - } - - // TODO: Check for the header section end without backtracking. - const int position = request->position; - int result = read_rest_of_line(request); - - if (0 == result) - { - return 0; - } - - if (2 == result - position) - { - // Header section ended, nothing left to parse. - return request->position + 2; - } - - request->position = position; - - if (0 >= request->headerv[request->headerc].name.start) - { - request->headerv[request->headerc].name.start = read_until_word(request); - - if (0 == request->headerv[request->headerc].name.start) - { - return 0; - } - } - - result = read_until_char(request, ':'); - - if (0 == result) - { - return 0; - } - - request->headerv[request->headerc].name.length = request->position - request->headerv[request->headerc].name.start; - request->step = parse_step_header_value; - - // Skip ':' - request->position++; - - return parse_step_header_value(request); -} - -/// Parses and verifies value of a single header from the request. -/// \param request Request to process -/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed -int parse_step_header_value(struct request * request) -{ - if (0 >= request->headerv[request->headerc].value.start) - { - request->headerv[request->headerc].value.start = read_until_word(request); - - if (0 == request->headerv[request->headerc].value.start) - { - return 0; - } - } - - const int result = read_rest_of_line(request); - - if (0 == result) - { - return 0; - } - - request->headerv[request->headerc].value.length = request->position - request->headerv[request->headerc].value.start; - request->step = parse_step_header_name; - request->headerc++; - - if (MAX_HEADERS == request->headerc) - { - return -1; - } - - return parse_step_header_name(request); -} @@ -1,31 +1,5 @@ #pragma once -struct span -{ - int start; - int length; -}; - -struct header -{ - struct span name; - struct span value; -}; - -struct request -{ - int (* step)(struct request *); - char * data; - int length; - int position; - struct span method; - struct span path; - struct span version; - struct header * headerv; - int headerc; - struct span body; -}; - enum status { STATUS_OK = 200, @@ -38,17 +12,3 @@ enum status }; extern const char * status_str[]; - -struct request * new_request(void); -void free_request(struct request *); - -int respond_only_status(int, enum status); -int respond_with_body(int, enum status, const char *, int); - -int parse_request(int, struct request **); - -int parse_step_method(struct request *); -int parse_step_path(struct request *); -int parse_step_version(struct request *); -int parse_step_header_name(struct request *); -int parse_step_header_value(struct request *); @@ -16,6 +16,8 @@ #include <lualib.h> #include "http.h" +#include "request.h" +#include "response.h" /// Tries to create, bind and start listening on INET server socket. /// \param node Hostname @@ -5,6 +5,7 @@ #include <lua.h> #include "http.h" +#include "request.h" int make_server(const char *, const char *); int handle_client(lua_State * L, struct pollfd *, struct request **, const int); diff --git a/request.c b/request.c new file mode 100644 index 0000000..f6f8a78 --- /dev/null +++ b/request.c @@ -0,0 +1,317 @@ +#include "request.h" + +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "http.h" + +static const int REQUEST_DATA_SIZE = 4096; +static const int MAX_HEADERS = 30; + +/// Allocates and initializes request structure. +/// \return Pointer to initialized request or NULL in case of an error +/// \see /free_request +struct request * new_request(void) +{ + struct request * request = malloc(sizeof(struct request)); + + if (NULL == request) + { + return NULL; + } + + memset(request, 0, sizeof(struct request)); + request->step = parse_step_method; + + request->data = malloc(REQUEST_DATA_SIZE); + + if (NULL == request->data) + { + free(request); + return NULL; + } + + return request; +} + +/// Releases memory used by the request and the request itself. +/// \param request Request to free +/// \see /new_request +void free_request(struct request * request) +{ + if (NULL != request->data) + { + free(request->data); + } + + if (NULL != request->headerv) + { + free(request->headerv); + } + + free(request); +} + +/// Collects request between calls to `poll`. +/// \param fd Client socket +/// \param request Pointer to current request context of handled client +/// \return Number of bytes parsed, -1 if an error occured or 0 if expects more data +int parse_request(const int fd, struct request ** request) +{ + if (NULL == *request) + { + *request = new_request(); + if (NULL == *request) + { + return -1; + } + } + + // TODO: Expand buffer until EAGAIN or arbitrary limit + int length = read(fd, (*request)->data, REQUEST_DATA_SIZE - 1); + + if (0 == length || (-1 == length && EWOULDBLOCK != errno && EAGAIN != errno)) + { + return -1; // TODO: Handle errors properly + } + + (*request)->data[length] = 0; + (*request)->length = length; + + return (*request)->step(*request); +} + + +/// Progresses the readout of the request until delimiter is found. +/// \param request Request to process +/// \param delimiter Character that marks the end of the readout +/// \return Position of the delimiter or 0 if the deliminter could not be found +static int read_until_char(struct request * request, const char delimiter) +{ + char character; + while (request->position < request->length) + { + character = request->data[request->position]; + if (delimiter == character) + { + return request->position; + } + request->position++; + } + return 0; +} + +/// Progresses the readout of the request until non-whitespace character is found. +/// \param request Request to process +/// \return Position of the first non-whitespace character or 0 if it could not be found +/// \see isalpha(3) +static int read_until_word(struct request * request) +{ + char character; + while (request->position < request->length) + { + character = request->data[request->position]; + if (!isspace(character)) + { + return request->position; + } + request->position++; + } + return 0; +} + +/// Progresses the readout of the request until line separator is found. +/// \param request Request to process +/// \return Position of the first byte of the separator or 0 if line separator could not be found +static int read_rest_of_line(struct request * request) +{ + while (request->position < request->length - 1) + { + if ('\r' == request->data[request->position] && '\n' == request->data[request->position + 1]) + { + return request->position; + } + else if ('\r' == request->data[request->position + 1]) + { + request->position += 1; + } + else + { + request->position += 2; + } + } + return 0; +} + +/// Parses method field of the request. +/// \param request Request to process +/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed +int parse_step_method(struct request * request) +{ + request->method.length = read_until_char(request, ' '); + + if (0 == request->method.length) + { + return 0; + } + + request->step = parse_step_path; + + return parse_step_path(request); +} + +/// Parses path field of the request. +/// \param request Request to process +/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed +// TODO: Consider spliting path into an actual path and arguments in this stage +int parse_step_path(struct request * request) +{ + if (0 >= request->path.start) + { + request->path.start = read_until_word(request); + + if (0 == request->path.start) + { + return 0; + } + } + + const int result = read_until_char(request, ' '); + + if (0 == result) + { + return 0; + } + + request->path.length = request->position - request->path.start; + request->step = parse_step_version; + + return parse_step_version(request); +} + +/// Parses and verifies http version field of the request. +/// \param request Request to process +/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed +// TODO: Return -1 if version is unsupported, meaning other than HTTP/1.1 +int parse_step_version(struct request * request) +{ + if (0 >= request->version.start) + { + request->version.start = read_until_word(request); + + if (0 == request->version.start) + { + return 0; + } + } + + const int result = read_rest_of_line(request); + + if (0 == result) + { + return 0; + } + + request->version.length = request->position - request->version.start; + request->step = parse_step_header_name; + + return parse_step_header_name(request); +} + +/// Parses and verifies name of a single header from the request. +/// \param request Request to process +/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed +int parse_step_header_name(struct request * request) +{ + if (NULL == request->headerv) + { + request->headerv = malloc(sizeof(struct header) * MAX_HEADERS); + request->headerc = 0; + memset(request->headerv, 0, sizeof(struct header) * MAX_HEADERS); + + if (NULL == request->headerv) + { + return -1; + } + } + + // TODO: Check for the header section end without backtracking. + const int position = request->position; + int result = read_rest_of_line(request); + + if (0 == result) + { + return 0; + } + + if (2 == result - position) + { + // Header section ended, nothing left to parse. + return request->position + 2; + } + + request->position = position; + + if (0 >= request->headerv[request->headerc].name.start) + { + request->headerv[request->headerc].name.start = read_until_word(request); + + if (0 == request->headerv[request->headerc].name.start) + { + return 0; + } + } + + result = read_until_char(request, ':'); + + if (0 == result) + { + return 0; + } + + request->headerv[request->headerc].name.length = request->position - request->headerv[request->headerc].name.start; + request->step = parse_step_header_value; + + // Skip ':' + request->position++; + + return parse_step_header_value(request); +} + +/// Parses and verifies value of a single header from the request. +/// \param request Request to process +/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed +int parse_step_header_value(struct request * request) +{ + if (0 >= request->headerv[request->headerc].value.start) + { + request->headerv[request->headerc].value.start = read_until_word(request); + + if (0 == request->headerv[request->headerc].value.start) + { + return 0; + } + } + + const int result = read_rest_of_line(request); + + if (0 == result) + { + return 0; + } + + request->headerv[request->headerc].value.length = request->position - request->headerv[request->headerc].value.start; + request->step = parse_step_header_name; + request->headerc++; + + if (MAX_HEADERS == request->headerc) + { + return -1; + } + + return parse_step_header_name(request); +} diff --git a/request.h b/request.h new file mode 100644 index 0000000..a302d61 --- /dev/null +++ b/request.h @@ -0,0 +1,38 @@ +#pragma once + +struct span +{ + int start; + int length; +}; + +struct header +{ + struct span name; + struct span value; +}; + +struct request +{ + int (* step)(struct request *); + char * data; + int length; + int position; + struct span method; + struct span path; + struct span version; + struct header * headerv; + int headerc; + struct span body; +}; + +struct request * new_request(void); +void free_request(struct request *); + +int parse_request(int, struct request **); + +int parse_step_method(struct request *); +int parse_step_path(struct request *); +int parse_step_version(struct request *); +int parse_step_header_name(struct request *); +int parse_step_header_value(struct request *); diff --git a/response.c b/response.c new file mode 100644 index 0000000..df465d0 --- /dev/null +++ b/response.c @@ -0,0 +1,43 @@ +#include "response.h" + +#include <stdio.h> +#include <unistd.h> + +#include "http.h" + +/// Sends a simple response only with a status to the client. +/// \param fd File descriptor of the client socket +/// \param status HTTP response status code +/// \return Negative value if an error was encountered; numbers of bytes written otherwise +int respond_only_status(const int fd, const enum status status) +{ + static const char * pattern = + "HTTP/1.1 %s\r\n" + "Connection: close\r\n" + "\r\n"; + + return dprintf(fd, pattern, status_str[status]); +} + +/// Sends a response with a status and a body to the client. +/// \param fd File descriptor of the client socket +/// \param status HTTP response status code +/// \param body Content that will be sent +/// \param size Size of the content in bytes +/// \return Negative value if an error was encountered; numbers of bytes written otherwise +int respond_with_body(const int fd, const enum status status, const char * body, const int size) +{ + static const char * pattern = + "HTTP/1.1 %s\r\n" + "Connection: close\r\n" + "Content-Type: application/json\r\n" + "Content-Size: %d\r\n" + "\r\n"; + + if (0 > dprintf(fd, pattern, status_str[status], size)) + { + return -1; // TODO: Handle errors properly + } + + return write(fd, body, size); +} diff --git a/response.h b/response.h new file mode 100644 index 0000000..ec90bc5 --- /dev/null +++ b/response.h @@ -0,0 +1,6 @@ +#pragma once + +#include "http.h" + +int respond_only_status(int, enum status); +int respond_with_body(int, enum status, const char *, int); |