diff --git a/README.md b/README.md index 9844b1d..313ba82 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,6 @@ make install clean ``` Run `make help` to see targets, flags and arguments. -Extensions ----------- -* [inja](https://github.com/pantor/inja) (template rendering, - requires [nlohmann/json](https://github.com/nlohmann/json)) - Examples -------- See [examples](examples). diff --git a/c/keyval.c b/c/keyval.c index 0a17d06..41ae2f8 100644 --- a/c/keyval.c +++ b/c/keyval.c @@ -3,7 +3,6 @@ #include "keyval.h" #include "utils.h" -#include #include #include @@ -20,6 +19,7 @@ static int rpd_keyval_realloc(rpd_keyval *keyval, int capacity); int rpd_keyval_init(rpd_keyval *keyval, int capacity) { keyval->size = keyval->capacity = 0; + keyval->unique = 1; if (capacity == 0) { keyval->items = NULL; @@ -46,7 +46,7 @@ int rpd_keyval_insert(rpd_keyval *keyval, const char *key, const char *value) } rpd_keyval_item *item = rpd_keyval_find(keyval, key); - if (item) { + if (item && keyval->unique) { free(item->key); item->key = NULL; if (item->val) { @@ -76,6 +76,36 @@ rpd_keyval_item *rpd_keyval_find(const rpd_keyval *keyval, const char *key) return NULL; } +rpd_keyval_item **rpd_keyval_findall(const rpd_keyval *keyval, const char *key) +{ + int i = 0, n = 0; + rpd_keyval_item **items = NULL; + while (i < keyval->size) { + if (!strcmp(keyval->items[i].key, key)) { + n++; + } + i++; + } + if (!n) + return NULL; + + items = malloc(sizeof(rpd_keyval_item *) * (n + 1)); + if (!items) + return NULL; + + items[n] = NULL; + i = 0, n = 0; + while (i < keyval->size) { + if (!strcmp(keyval->items[i].key, key)) { + items[n] = keyval->items + i; + n++; + } + i++; + } + + return items; +} + void rpd_keyval_cleanup(rpd_keyval *keyval) { if (keyval->items) { diff --git a/c/response.c b/c/response.c index 66c963a..4125582 100644 --- a/c/response.c +++ b/c/response.c @@ -6,22 +6,25 @@ #include #include -static size_t calc_res_headers_sz(const rpd_res *res); +static size_t calc_res_status_sz(const rpd_res *res); + +static size_t calc_res_headers_sz(const rpd_keyval *res); void rpd_res_init(rpd_res *dest) { dest->status = rpd_res_st_ok; - dest->location = dest->content_type = NULL; dest->body = NULL; - - rpd_keyval_init(&dest->cookie, 0); + rpd_keyval_init(&dest->headers, 5); + dest->headers.unique = 0; } int rpd_res_headers_str(char **dest, const rpd_res *src) { - size_t size = calc_res_headers_sz(src); + size_t size, i = 0; char *ptr; + size = calc_res_headers_sz(&src->headers); + *dest = (char *) malloc(sizeof(char) * size); if (!*dest) { perror("malloc"); @@ -29,12 +32,13 @@ int rpd_res_headers_str(char **dest, const rpd_res *src) } ptr = *dest; - if (src->content_type) { - ptr += sprintf(ptr, "Content-Type: %s\r\n", src->content_type); - } - - if (src->location) { - ptr += sprintf(ptr, "Location: %s\r\n", src->location); + while (i < src->headers.size) { + ptr += sprintf( + ptr, + "%s: %s\r\n", + src->headers.items[i].key, + src->headers.items[i].val); + i++; } return 0; @@ -44,7 +48,9 @@ int rpd_res_str(char **dest, const rpd_res *res) { size_t headers_size, size; char *ptr, *headers; - size = headers_size = calc_res_headers_sz(res); + size = headers_size = calc_res_headers_sz(&res->headers); + size += calc_res_status_sz(res); + if (res->body) size += 2 + strlen(res->body); @@ -78,25 +84,24 @@ int rpd_res_str(char **dest, const rpd_res *res) return 0; } -static size_t calc_res_headers_sz(const rpd_res *res) +static size_t calc_res_status_sz(const rpd_res *res) { size_t size = 0; + size += strlen("Status: \r\n"); + size += 3; /* plus status code */ + return size; +} - size += strlen("Status: \r\n") + 3; - if (res->location) { - size += strlen("Location: \r\n") + strlen(res->location); - } - - if (res->content_type) { - size += strlen("Content-Type: \r\n") + strlen(res->content_type); - } - - if (res->cookie.size) { - size += strlen("Set-Cookie: \r\n") * res->cookie.size; - for (int i = 0; i < res->cookie.size; i++) { - rpd_keyval_item *item = res->cookie.items + i; - size += strlen(item->key) + strlen(item->val); - } +static size_t calc_res_headers_sz(const rpd_keyval *headers) +{ + size_t size = 0, i = 0; + + while (i < headers->size) { + size += strlen(headers->items[i].key); + size += 2; /* plus ": " */ + size += strlen(headers->items[i].val); + size += 2; /* plus CRLF */ + i++; } return size; @@ -106,22 +111,12 @@ void rpd_res_cleanup(rpd_res *res) { res->status = rpd_res_st_ok; - if (res->location) { - free(res->location); - res->location = NULL; - } - - if (res->content_type) { - free(res->content_type); - res->content_type = NULL; - } - if (res->body) { free(res->body); res->body = NULL; } - if (res->cookie.capacity) { - rpd_keyval_cleanup(&res->cookie); + if (res->headers.capacity) { + rpd_keyval_cleanup(&res->headers); } } diff --git a/c/utils.c b/c/utils.c index 174ed2a..0df63b3 100644 --- a/c/utils.c +++ b/c/utils.c @@ -2,6 +2,7 @@ /* Copyright 2022 Ivan Polyakov */ #include "utils.h" +#include #include #include @@ -43,3 +44,63 @@ char *rpd_strsep(char **str, const char *sep) *str = end; return s; } + +const char *rpd_splitbyc(char **dest1, char **dest2, const char *src, const char sep) +{ + const char *start = src, *end = src; + + if (!src) + return 0; + + while (*end) { + if (*end == sep) { + size_t len = end - start; + *dest1 = malloc(sizeof(char) * (len + 1)); + if (!*dest1) { + perror("malloc"); + return NULL; + } + memcpy(*dest1, start, len); + (*dest1)[len] = '\0'; + + end++; + len = strlen(start) - (end - start); + if (!len) { + *dest2 = NULL; + return 0; + } + + *dest2 = malloc(sizeof(char) * (len + 1)); + if ((!*dest2)) { + perror("malloc"); + return NULL; + } + memcpy(*dest2, end, len); + (*dest2)[len + 1] = '\0'; + return 0; + } + end++; + } + + return 0; +} + +void rpd_strerase(char *src, int nchars) +{ + char *ptr = NULL; + size_t len = strlen(src); + if (!src) + return; + + ptr = src + nchars; + while (*ptr != '\0') { + *(ptr - nchars) = *ptr; + ptr++; + } + + ptr = src + len; + while (ptr >= (src + len) - nchars) { + *ptr = '\0'; + ptr--; + } +} diff --git a/c/utils.h b/c/utils.h index 53c6430..a2e3008 100644 --- a/c/utils.h +++ b/c/utils.h @@ -10,4 +10,21 @@ char *rpd_strdup(const char *src); char *rpd_strsep(char **str, const char *sep); +const char *rpd_splitbyc(char **dest1, char **dest2, const char *src, const char sep); + +/*! + * \brief Erases part of the string. + * + * This function moves characters to the beginning of the string + * and inserts '\0' at the original position without reallocation. + * + * To erase characters not from beginning of the string, + * you can pass a pointer to the beginning of the desired + * part of the string. + * + * \param src String to erase. + * \param nchars Number of charecters to erase. + */ +void rpd_strerase(char *src, int nchars); + #endif /* RAPIDA_UTILS_H_ENTRY */ diff --git a/config.mk b/config.mk index 422e1f4..1ea7d81 100644 --- a/config.mk +++ b/config.mk @@ -1,4 +1,4 @@ -VERSION=0.3.1 +VERSION=0.4 #arg Installation prefix PREFIX=/usr/local @@ -31,16 +31,6 @@ ifeq ($(FCGI_SERVER), 1) LDFLAGS += -lfcgi endif -#flag Enable inja extension -EXTENSIONS_INJA ?= 0 -#arg Dist path. Needed only if inja is enabled. -DIST_PATH = /var/www/html -ifneq ($(EXTENSIONS_INJA), 0) - CXXFLAGS+=-DEXTENSIONS_INJA -DDIST_PATH=\"$(DIST_PATH)\" - CXXSTD=-std=c++17 -endif - - #flag Multithread support MT_ENABLED ?= 0 #arg Number of threads. 8 by default. diff --git a/cxx/Request.cxx b/cxx/Request.cxx new file mode 100644 index 0000000..b8ff622 --- /dev/null +++ b/cxx/Request.cxx @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* Copyright 2022 Ivan Polyakov */ + +#include "Request.hxx" + +using namespace rpd; + +const char *Request::header(const char *key) const +{ + rpd_keyval_item *hi = rpd_keyval_find(&req->headers, key); + return hi ? hi->val : NULL; +} diff --git a/cxx/Response.cxx b/cxx/Response.cxx index 0917f18..784cbcc 100644 --- a/cxx/Response.cxx +++ b/cxx/Response.cxx @@ -3,34 +3,12 @@ #include "Response.hxx" -#ifdef EXTENSIONS_INJA -#include -#endif - using namespace rpd; -#ifdef EXTENSIONS_INJA -void Response::render(const char *path, nlohmann::json data) +int Response::header(const char *key, const char *value) { - inja::Environment env; - inja::Template tpl; - - try { - std::string tplpath = DIST_PATH; - tplpath += path; - tpl = env.parse_template(tplpath); - } catch (inja::FileError &e) { - std::cerr << e.what() << std::endl; - return; - } - - try { - std::string result = env.render(tpl, data); - body(result.c_str()); - } catch (inja::RenderError &e) { - std::cerr << e.what() << std::endl; - status(rpd_res_st_internal_server_error); - return; - } + return rpd_keyval_insert( + &this->res->headers, + key, + value); } -#endif diff --git a/examples/c/example.c b/examples/c/example.c index b3a2089..4770985 100644 --- a/examples/c/example.c +++ b/examples/c/example.c @@ -28,6 +28,7 @@ static void products_handler(rpd_req *req, rpd_res *res, void *userdata) int bufflen = strlen(body) + strlen(cat->val) + strlen(id->val); res->body = (char *) malloc(sizeof(char) * (bufflen + 1)); if (!res->body) { + perror("malloc"); res->status = rpd_res_st_internal_server_error; return; } @@ -37,12 +38,7 @@ static void products_handler(rpd_req *req, rpd_res *res, void *userdata) res->status = rpd_res_st_ok; - /* - * place all values in heap, please, - * because after call handler - * `req` and `res` will be freed - */ - res->content_type = strdup("text/html"); + rpd_keyval_insert(&res->headers, "Content-Type", "text/html"); } int main() diff --git a/examples/c/minimal.c b/examples/c/minimal.c index ec4583a..67cbfc8 100644 --- a/examples/c/minimal.c +++ b/examples/c/minimal.c @@ -15,15 +15,17 @@ static void home_page_handler(rpd_req *req, rpd_res *res, void *userdata) /* Check request method */ switch (req->method) { case HEAD: - /* Process GET request */ res->status = rpd_res_st_ok; + rpd_keyval_insert(&res->headers, "Content-Type", "text/plain"); + break; + case GET: + res->status = rpd_res_st_ok; + rpd_keyval_insert(&res->headers, "Content-Type", "text/plain"); - /* Please allocate data on the heap, + /* Please allocate body on the heap, * because after calling this handler - * Rapida will free it all. + * Rapida will free it. */ - res->content_type = strdup("text/plain"); - case GET: res->body = strdup("Hello World!"); break; default: diff --git a/examples/cxx/example.cxx b/examples/cxx/example.cxx index 85480b3..6cc303e 100644 --- a/examples/cxx/example.cxx +++ b/examples/cxx/example.cxx @@ -35,7 +35,7 @@ protected: virtual void handle_head(const rpd::Request &req, rpd::Response &res) override { res.status(rpd_res_st_ok); - res.content_type("text/html"); + res.header("Content-Type", "text/html"); } }; diff --git a/examples/cxx/minimal.cxx b/examples/cxx/minimal.cxx index d8febda..3bb0fa1 100644 --- a/examples/cxx/minimal.cxx +++ b/examples/cxx/minimal.cxx @@ -1,7 +1,6 @@ #include "../../include/rapida.hxx" #include "../../include/servers/tcp.h" - /* * \brief Home page route handler. */ @@ -29,7 +28,7 @@ protected: virtual void handle_head(const rpd::Request &req, rpd::Response &res) override { res.status(rpd_res_st_ok); - res.content_type("text/plain"); + res.header("Content-Type", "text/plain"); } }; diff --git a/include/KeyVal.hxx b/include/KeyVal.hxx index ace50e2..3adfdde 100644 --- a/include/KeyVal.hxx +++ b/include/KeyVal.hxx @@ -36,6 +36,24 @@ public: _keyval = keyval; } + /*! + * \brief Is the storage unique? + */ + bool unique() const + { + return _keyval->unique; + } + + /*! + * \brief Sets the uniqueness flag. + * + * \param is_unique Uniqueness flag. + */ + void unique(bool is_unique) + { + _keyval->unique = is_unique; + } + /*! * \brief Returns real key-value storage. * diff --git a/include/Request.hxx b/include/Request.hxx index dfa6d94..2a5e89e 100644 --- a/include/Request.hxx +++ b/include/Request.hxx @@ -69,24 +69,23 @@ public: } /*! - * \brief Gets authorization string. + * \brief Gets the request header. * - * \return Authorization string. + * \params key Header key. + * + * \return Found header or NULL. */ - const char *authorization() const - { - return req->auth; - } + const char *header(const char *key) const; /*! - * \brief Gets cookie string. + * \brief Gets all request headers. * - * \return Cookie string. + * \return Key-value pairs. */ - const char *cookie() const + KeyVal headers() const { - return req->cookie; - }; + return &req->headers; + } /*! * \brief Gets request body content. diff --git a/include/Response.hxx b/include/Response.hxx index 75db59a..2ef0c7a 100644 --- a/include/Response.hxx +++ b/include/Response.hxx @@ -8,10 +8,6 @@ #include #include -#ifdef EXTENSIONS_INJA -#include -#endif - namespace rpd { /*! * \brief C++ response wrapper. @@ -59,43 +55,14 @@ public: } /*! - * \brief Sets _location_ response field. - * - * \param location Location URL. - */ - void location(const char *location) - { - if (res->location) - free(res->location); - res->location = strdup(location); - } - - /*! - * \brief Sets _Content-Type_ response field. - * - * \param content_type Response content type. - */ - void content_type(const char *content_type) - { - if (res->content_type) - free(res->content_type); - res->content_type = strdup(content_type); - } - - /*! - * \brief Sets cookie field. + * \brief Sets response header. * - * Adds new cookie field to key-value storage. - * If you need to set cookie parameters such as lifetime, - * place it to val. + * \param key Header key. + * \param value Header value * - * \param key Cookie field key. - * \param val Cookie field value. + * \return Status code. 0 is success. */ - void cookie(const char *key, const char *val) - { - rpd_keyval_insert(&res->cookie, key, val); - } + int header(const char *key, const char *value); /*! * \brief Sets response body. @@ -109,15 +76,6 @@ public: res->body = strdup(body); } -#ifdef EXTENSIONS_INJA - /*! - * \brief Render data to HTML template. - * \param path Path to HTML template relative to dist location. - * \param data Template data to interpolate. - */ - void render(const char *path, nlohmann::json data); -#endif - /*! * \brief Destructor. */ diff --git a/include/keyval.h b/include/keyval.h index 60c270b..a532019 100644 --- a/include/keyval.h +++ b/include/keyval.h @@ -9,7 +9,7 @@ #ifndef RAPIDA_KEYVAL_H_ENTRY #define RAPIDA_KEYVAL_H_ENTRY -#include "unistd.h" +#include #ifdef __cplusplus extern "C" { @@ -30,6 +30,7 @@ typedef struct { rpd_keyval_item *items; /**< Key-value pairs array. */ int size; /**< Number of elements in rpd_keyval::items. */ int capacity; /**< Current capacity (allocated memory). */ + int unique; /**< Unique flag */ } rpd_keyval; /*! @@ -70,6 +71,21 @@ int rpd_keyval_insert(rpd_keyval *keyval, const char *key, const char *value); */ rpd_keyval_item *rpd_keyval_find(const rpd_keyval *keyval, const char *key); +/*! + * \brief Finds all key-value pair by key. + * + * Useful when storage is not unique. + * In request header fields, for example. + * + * Last item will be NULL. + * + * \param keyval Key-value storage instance. + * \param key Key to search. + * + * \return Found items or NULL. + */ +rpd_keyval_item **rpd_keyval_findall(const rpd_keyval *keyval, const char *key); + /*! * \brief Free key-val pairs and reset. * diff --git a/include/request.h b/include/request.h index af3eca3..5e1cb14 100644 --- a/include/request.h +++ b/include/request.h @@ -36,10 +36,9 @@ enum rpd_req_methods { */ typedef struct { enum rpd_req_methods method; /**< Request method. */ - char *auth; /**< Authorization field. */ - char *cookie; /**< Cookie field. */ char *body; /**< Body field. */ rpd_url path; /**< Requested URL. */ + rpd_keyval headers; /**< Request headers. */ rpd_keyval query; /**< Query. */ rpd_keyval params; /**< Dynamic parameters. */ } rpd_req; diff --git a/include/response.h b/include/response.h index 6e6d64e..3cd949a 100644 --- a/include/response.h +++ b/include/response.h @@ -86,10 +86,8 @@ enum rpd_res_statuses { */ typedef struct { enum rpd_res_statuses status; /**< Response status code. */ - char *location; /**< Location field. */ - char *content_type; /**< Content type. */ char *body; /**< Response body. */ - rpd_keyval cookie; /**< Set-Cookie fields. */ + rpd_keyval headers; /**< Response headers. */ } rpd_res; /*! diff --git a/servers/fcgi.c b/servers/fcgi.c index 1d3d335..e8dec1b 100644 --- a/servers/fcgi.c +++ b/servers/fcgi.c @@ -2,7 +2,11 @@ /* Copyright 2022 Ivan Polyakov */ #include "../include/servers/fcgi.h" +#include "../c/utils.h" +#include +#include #include +#include #ifdef MT_ENABLED #include @@ -56,6 +60,8 @@ static int read_fcgx_req_body(char **dest, FCGX_Request *req); */ static void send_response(rpd_res *res, FCGX_Stream *out); +static int env_to_req_header(char **key, char **val, const char *envp); + int rpd_fcgi_server_start(rpd_app *app, const char *sock_path) { FCGX_Init(); @@ -155,9 +161,40 @@ static int fcgx_to_rpd_req(rpd_req *dest, FCGX_Request *req) dest->method = rpd_req_smethod(FCGX_GetParam("REQUEST_METHOD", req->envp)); rpd_url_parse(&dest->path, FCGX_GetParam("DOCUMENT_URI", req->envp)); - dest->auth = FCGX_GetParam("HTTP_AUTHORIZATION", req->envp); - dest->cookie = FCGX_GetParam("HTTP_COOKIE", req->envp); + // dest->auth = FCGX_GetParam("HTTP_AUTHORIZATION", req->envp); + // dest->cookie = FCGX_GetParam("HTTP_COOKIE", req->envp); rpd_keyval_init(&dest->params, 0); + rpd_keyval_init(&dest->headers, 0); + dest->headers.unique = 0; + + char **env = req->envp; + char *key = NULL, *val = NULL; + while (*(++env)) { + char *ptr = NULL; + + /* keep only http request fields */ + ptr = strstr(*env, "HTTP"); + if (ptr == NULL || ptr - *env != 0) + continue; + + env_to_req_header(&key, &val, *env); + if (!val || !key) + continue; + rpd_strerase(key, 5); + ptr = key; + while (*ptr) { + *ptr = tolower(*ptr); + if (ptr == key || *(ptr - 1) == '-') + *ptr = toupper(*ptr); + if (*ptr == '_') + *ptr = '-'; + ptr++; + } + + rpd_keyval_insert(&dest->headers, key, val); + free(key); + free(val); + } if (dest->method != GET) { if (read_fcgx_req_body(&dest->body, req)) { @@ -186,3 +223,19 @@ static int read_fcgx_req_body(char **dest, FCGX_Request *req) return 0; } + +static int env_to_req_header(char **key, char **val, const char *env) +{ + const char *ptr = NULL; + + rpd_splitbyc(key, val, env, '='); + if (!*key || !*val) { + if (*key) + free(*key); + if (*val) + free(*val); + return 1; + } + + return 0; +} diff --git a/servers/tcp.c b/servers/tcp.c index 89e4df6..1093e9f 100644 --- a/servers/tcp.c +++ b/servers/tcp.c @@ -45,6 +45,23 @@ static int mg_to_rpd_req(rpd_req *req, struct mg_http_message *msg) rpd_query_parse(&req->query, tmp); } + size_t i, max = sizeof(msg->headers) / sizeof(msg->headers[0]); + rpd_keyval_init(&req->headers, max); + req->headers.unique = 0; + + // Iterate over request headers + char *key = NULL, *val = NULL; + for (i = 0; i < max && msg->headers[i].name.len > 0; i++) { + struct mg_str *k = &msg->headers[i].name, *v = &msg->headers[i].value; + + mg_str_alloc(&key, *k); + mg_str_alloc(&val, *v); + + rpd_keyval_insert(&req->headers, key, val); + } + free(key); + free(val); + free(tmp); return 0; } diff --git a/tests/app.cxx b/tests/app.cxx index e49e2b5..7c4cd7f 100644 --- a/tests/app.cxx +++ b/tests/app.cxx @@ -9,7 +9,7 @@ using namespace rpd; TEST_CASE("Application") { rpd_app app; - int res = rpd_app_create(&app, "/tmp/rapida.test.socket"); + int res = rpd_app_create(&app); SECTION("App creation") { diff --git a/tests/keyval.cxx b/tests/keyval.cxx index a217efe..3fa95df 100644 --- a/tests/keyval.cxx +++ b/tests/keyval.cxx @@ -36,6 +36,24 @@ TEST_CASE("Key-value storage") REQUIRE(std::string(item->val) == "val"); } + SECTION("Passing duplicates to non-unique storage and finding them") + { + int sz = keyval.size; + keyval.unique = 0; + + rpd_keyval_insert(&keyval, "Set-Cookie", "param=val"); + rpd_keyval_insert(&keyval, "Set-Cookie", "param1=val1"); + REQUIRE(keyval.size == sz + 2); + + rpd_keyval_item **cookies = rpd_keyval_findall(&keyval, "Set-Cookie"); + REQUIRE(cookies != NULL); + REQUIRE(std::string(cookies[0]->key) == "Set-Cookie"); + REQUIRE(std::string(cookies[0]->val) == "param=val"); + REQUIRE(std::string(cookies[1]->key) == "Set-Cookie"); + REQUIRE(std::string(cookies[1]->val) == "param1=val1"); + REQUIRE(cookies[2] == NULL); + } + SECTION("Cleanup") { rpd_keyval_cleanup(&keyval);