/* SPDX-License-Identifier: GPL-3.0-or-later */ /* Copyright 2022 Ivan Polyakov */ #include "../include/servers/fcgi.h" #include "../c/utils.h" #include #include #include #include #ifdef MT_ENABLED #include #endif /* MT_ENABLED */ typedef struct { rpd_app *app; int sock_id; } thread_data; /*! * \brief Run FastCGI requests listening in loop. * \param data \see thread_data. * \return NULL; */ static void *listen_requests(void *data); /*! * \brief Handle Request. * * This function converts FastCGI request into the Rapida request * and calls Rapida request handler. * * \param app Application instance. * \param fcgx_req FastCGI Request. */ static void handle_request(rpd_app *app, FCGX_Request *fcgx_req); /*! * \brief Convert FastCGI request into the Rapida request. * * \param dest Rapida request. * \param req FastCGI request. * * \return Status code. 0 is success. */ static int fcgx_to_rpd_req(rpd_req *dest, FCGX_Request *req); /*! * Read FastCGI request body. * \param dest Destination buffer. * \param req FastCGI request. * \return Status code. 0 is success. */ static int read_fcgx_req_body(char **dest, FCGX_Request *req); /*! * \brief Sends response to client. * \param res Response instance. * \param out Output stream. */ 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(); int sock_id = 0; if ((sock_id = FCGX_OpenSocket(sock_path, 10)) < 0) { return 1; } thread_data data = { app, sock_id }; #ifdef MT_ENABLED pthread_t threads[NTHREADS]; for (int i = 0; i < NTHREADS; i++) { pthread_create(&threads[i], 0, &listen_requests, (void *) &data); pthread_join(threads[i], 0); } #else listen_requests((void *) &data); #endif return 0; } static void *listen_requests(void *data) { thread_data *hdata = (thread_data *) data; FCGX_Request req; if (FCGX_InitRequest(&req, hdata->sock_id, 0)) return 0; hdata->app->running = 1; while (hdata->app->running) { #ifdef MT_ENABLED static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&accept_mutex); #endif int rc = FCGX_Accept_r(&req); #ifdef MT_ENABLED pthread_mutex_unlock(&accept_mutex); #endif if (rc < 0) break; #ifdef MT_ENABLED static pthread_mutex_t handle_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&handle_mutex); #endif handle_request(hdata->app, &req); #ifdef MT_ENABLED pthread_mutex_unlock(&handle_mutex); #endif FCGX_Finish_r(&req); } return NULL; } static void handle_request(rpd_app *app, FCGX_Request *fcgx_req) { rpd_req *req; rpd_res *res; req = (rpd_req *) malloc(sizeof(rpd_req)); res = (rpd_res *) malloc(sizeof(rpd_res)); fcgx_to_rpd_req(req, fcgx_req); rpd_res_init(res); rpd_app_handle_request(app, req, res); send_response(res, fcgx_req->out); rpd_req_cleanup(req); rpd_res_cleanup(res); free(req); free(res); } static void send_response(rpd_res *res, FCGX_Stream *out) { char *buff; if (rpd_res_str(&buff, res)) { FCGX_PutS("Status: 500\r\n\r\n", out); return; } FCGX_PutS(buff, out); free(buff); } static int fcgx_to_rpd_req(rpd_req *dest, FCGX_Request *req) { rpd_query_parse(&dest->query, FCGX_GetParam("QUERY_STRING", req->envp)); 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); rpd_keyval_init(&dest->params, 0); rpd_keyval_init(&dest->headers, 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)) { return 2; } } else { dest->body = NULL; } return 0; } static int read_fcgx_req_body(char **dest, FCGX_Request *req) { char *clen = FCGX_GetParam("CONTENT_LENGTH", req->envp); if (!clen) return 1; size_t len = atoll(clen); *dest = (char *) malloc(sizeof(char) * (len + 1)); if (!*dest) return 2; *dest[len] = '\0'; FCGX_GetStr(*dest, len, req->in); 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; }