#include "request.h" #include #include #include #include #include #include #include #include #include static const int REQUEST_DATA_SIZE = 4096; /// Allocates and initializes request structure. /// \return Pointer to initialized request or NULL in case of an error /// \see /free_request struct request * new_request(lua_State * L) { 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; } request->lua = lua_newthread(L); request->reference = luaL_ref(L, LUA_REGISTRYINDEX); return request; } /// Releases memory used by the request and the request itself. /// \param request Request to free /// \see /new_request void free_request(lua_State * L, struct request * request) { if (NULL != request->data) { free(request->data); } luaL_unref(L, LUA_REGISTRYINDEX, request->reference); 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(lua_State * L, const int fd, struct request ** request) { if (NULL == *request) { *request = new_request(L); 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; } #define PROCEED_TO(_x) \ request->temp.start = 0; \ request->temp.length = 0; \ request->step = _x; \ return _x(request) /// 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) { // TODO: Consider reworking read_* functions to be more aware of word boundaries. request->temp.length = read_until_char(request, ' '); if (0 == request->temp.length) { return 0; } lua_pushlstring(request->lua, request->data + request->temp.start, request->temp.length); PROCEED_TO(parse_step_path); } /// 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) { // TODO: Rethink interface of parse_step_* and read_* functions. After a break it looks like mess; it's a bad sign. if (0 >= request->temp.start) { request->temp.start = read_until_word(request); if (0 == request->temp.start) { return 0; } } const int result = read_until_char(request, ' '); if (0 == result) { return 0; } request->temp.length = request->position - request->temp.start; // TODO: Use macro for pushing temporary span. Also clear the span in that macro instead of PROCEED_TO. lua_pushlstring(request->lua, request->data + request->temp.start, request->temp.length); PROCEED_TO(parse_step_version); } /// 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->temp.start) { request->temp.start = read_until_word(request); if (0 == request->temp.start) { return 0; } } const int result = read_rest_of_line(request); if (0 == result) { return 0; } request->temp.length = request->position - request->temp.start; lua_pushlstring(request->lua, request->data + request->temp.start, request->temp.length); PROCEED_TO(parse_step_header_name); } /// 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 (0 == lua_istable(request->lua, -1)) { lua_newtable(request->lua); } if (request->length - request->position < 4) { return 0; } if ( request->data[request->position] == '\r' && request->data[request->position + 1] == '\n' && request->data[request->position + 2] == '\r' && request->data[request->position + 3] == '\n') { // Skip "\r\n\r\n", end of headers. request->position += 4; // Early return hidden in a macro! PROCEED_TO(parse_step_data); } if (0 >= request->temp.start) { request->temp.start = read_until_word(request); if (0 == request->temp.start) { return 0; } } int result = read_until_char(request, ':'); if (0 == result) { printf("parse_step_header_name:no ':'\n"); return 0; } request->temp.length = request->position - request->temp.start; if (0 == strncasecmp("content-length", request->data + request->temp.start, request->temp.length)) { // TODO: While it shouldn't happen, use a flag instead of a magic value. request->expected_data_length = -1; } lua_pushlstring(request->lua, request->data + request->temp.start, request->temp.length); // Skip ':' request->position++; PROCEED_TO(parse_step_header_value); } /// 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->temp.start) { request->temp.start = read_until_word(request); if (0 == request->temp.start) { return 0; } } const int result = read_rest_of_line(request); if (0 == result) { return 0; } request->temp.length = request->position - request->temp.start; if (-1 == request->expected_data_length) { *(request->data + request->temp.start + request->temp.length) = 0; request->expected_data_length = atoi(request->data + request->temp.start); *(request->data + request->temp.start + request->temp.length) = '\r'; } lua_pushlstring(request->lua, request->data + request->temp.start, request->temp.length); lua_rawset(request->lua, -3); PROCEED_TO(parse_step_header_name); } /// Makes sure that the expected data is in payload and pushes it forward to Lua. /// \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_data(struct request * request) { const int bytes_left = request->length - request->position; if (bytes_left > request->expected_data_length) { return -1; } else if (bytes_left < request->expected_data_length) { return 0; } lua_pushlstring(request->lua, request->data + request->position, request->expected_data_length); if (NULL != request->data) { free(request->data); request->data = NULL; } return request->position; } // TODO: Make a table from the request.