|
|
|
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
|
|
|
/* Copyright 2022 Ivan Polyakov */
|
|
|
|
|
|
|
|
#include "../include/servers/fcgi.h"
|
|
|
|
#include "../c/utils.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#ifdef MT_ENABLED
|
|
|
|
#include <pthread.h>
|
|
|
|
#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);
|
|
|
|
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)) {
|
|
|
|
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;
|
|
|
|
}
|