Winix framework is a complete infrastructure written in the C++ programming language to create modern web applications and APIs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

2786 lines
62 KiB

/*
* This file is a part of Winix
* and is distributed under the 2-Clause BSD licence.
* Author: Tomasz Sowa <t.sowa@ttmath.org>
*/
/*
* Copyright (c) 2010-2021, Tomasz Sowa
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <utility>
#include <fastcgi.h>
#include <stddef.h>
#include "app.h"
#include "plugin.h"
#include "misc.h"
#include "functions/functions.h"
#include "utf8/utf8.h"
#include "convert/convert.h"
#include "models/migration.h"
namespace Winix
{
App::App()
{
stdout_is_closed = false;
last_sessions_save = std::time(0);
fcgi_socket = -1;
file_log.set_synchro(&synchro);
file_log.set_time_zones(&system.time_zones);
log.set_log_buffer(&log_buffer);
log.set_file_log(&file_log);
// objects dependency for main thread
winix_base.set_config(&config);
winix_base.set_log_buffer(&log_buffer);
winix_base.set_file_log(&file_log);
winix_base.set_synchro(&synchro);
winix_model.set_dependency(&winix_base);
winix_model.set_plugin(&plugin);
winix_model.set_model_connector(&model_connector);
winix_system.set_dependency(&winix_model);
winix_system.set_system(&system);
winix_request.set_dependency(&winix_system);
winix_request.set_cur(&cur);
winix_request.set_session_manager(&session_manager);
winix_request.set_locale(&TemplatesFunctions::locale);
winix_request.set_model_connector(&model_connector);
// //////////////////////////////////
config.SetFileLog(&file_log);
config.SetLogBuffer(&log_buffer);
// temporary there is only one request
cur.request = &req;
cur.session = session_manager.GetTmpSession();
cur.mount = system.mounts.GetEmptyMount();
db_conn.set_dependency(&winix_base);
db.set_dependency(&winix_base);
db.SetConn(db_conn);
plugin.set_dependency(&winix_base);
plugin.SetDb(&db);
//plugin.SetConfig(&config);
plugin.SetCur(&cur);
plugin.SetSystem(&system);
plugin.SetFunctions(&functions);
plugin.SetTemplates(&templates);
//plugin.SetSynchro(&synchro);
plugin.SetSessionManager(&session_manager);
plugin.SetWinixRequest(&winix_request);
req.SetConfig(&config);
req.set_connector(&model_connector);
functions.set_dependency(&winix_request);
// functions.set_config(&config);
// functions.set_file_log(&file_log);
// functions.set_model_connector(&model_connector);
// functions.set_synchro(&synchro);
//functions.SetConfig(&config);
functions.SetCur(&cur);
functions.SetDb(&db);
functions.SetSystem(&system);
functions.SetTemplates(&templates);
//functions.SetSynchro(&synchro);
functions.SetSessionManager(&session_manager);
system.set_dependency(&winix_model);
//system.SetConfig(&config);
system.SetCur(&cur);
system.SetDb(&db);
//system.SetSynchro(&synchro);
system.SetFunctions(&functions);
system.SetSessionManager(&session_manager);
templates.set_dependency(&winix_request);
templates.SetConfig(&config);
templates.SetCur(&cur);
templates.SetDb(&db);
templates.SetSystem(&system);
templates.SetFunctions(&functions);
templates.SetSessionManager(&session_manager);
session_manager.set_dependency(&winix_model); // zobaczyc czy wskoczy do przeciazonej metody w session manager
session_manager.SetLastContainer(&system.users.last);
session_manager.SetCur(&cur);
session_manager.SetSystem(&system);
post_multi_parser.set_dependency(&winix_base);
post_multi_parser.SetConfig(&config);
}
void App::InitLoggers()
{
file_log.init(config.log_file, config.log_stdout, config.log_level, config.log_save_each_line, config.log_time_zone_id);
log.SetMaxRequests(config.log_request);
}
Log & App::GetMainLog()
{
return log;
}
void App::InitPlugins()
{
plugin.LoadPlugins(config.plugins_dir, config.plugin_file);
}
bool App::InitFCGI(char * sock, char * sock_user, char * sock_group)
{
if( !wide_to_utf8(config.fcgi_socket, sock, WINIX_OS_PATH_SIZE) )
return false;
if( !wide_to_utf8(config.fcgi_socket_user, sock_user, WINIX_OS_USERNAME_SIZE) )
return false;
if( !wide_to_utf8(config.fcgi_socket_group, sock_group, WINIX_OS_USERNAME_SIZE) )
return false;
return true;
}
/*
* chmod and chown of the socket are set before winix drops privileges
*/
bool App::InitFCGIChmodChownSocket(char * sock, char * sock_user, char * sock_group)
{
if( chmod(sock, config.fcgi_socket_chmod) < 0 )
{
log << log1 << "App: I cannot chmod a FastCGI socket, check fcgi_socket_chmod in the config" << logend;
return false;
}
passwd * pw = getpwnam(sock_user);
if( !pw )
{
log << log1 << "App: there is no a user: " << config.fcgi_socket_user << logend;
return false;
}
group * gr = getgrnam(sock_group);
if( !gr )
{
log << log1 << "App: there is no a group: " << config.fcgi_socket_group << logend;
return false;
}
if( chown(sock, pw->pw_uid, gr->gr_gid) < 0 )
{
log << log1 << "App: I cannot chown a FastCGI socket, check fcgi_socket_user "
<< "and fcgi_socket_group in the config" << logend;
return false;
}
return true;
}
bool App::InitFCGI()
{
char sock[WINIX_OS_PATH_SIZE];
char sock_user[WINIX_OS_USERNAME_SIZE];
char sock_group[WINIX_OS_USERNAME_SIZE];
if( !InitFCGI(sock, sock_user, sock_group) )
return false;
unlink(sock);
fcgi_socket = FCGX_OpenSocket(sock, config.fcgi_socket_listen);
if( fcgi_socket < 0 )
{
log << log1 << "App: An error during creating a fcgi socket" << logend;
return false;
}
log << log3 << "App: FastCGI socket number: " << fcgi_socket << logend;
if( !InitFCGIChmodChownSocket(sock, sock_user, sock_group) )
return false;
if( FCGX_Init() != 0 )
{
log << log1 << "App: FCGX_Init fails" << logend;
return false;
}
if( FCGX_InitRequest(&fcgi_request, fcgi_socket, FCGI_FAIL_ACCEPT_ON_INTR) != 0 )
{
log << log1 << "App: FCGX_InitRequest fails" << logend;
return false;
}
return true;
}
bool App::DoDatabaseMigration()
{
bool ok = true;
Migration migration;
User user;
ItemContent item_content;
Item item;
Group group;
ok = ok && Migration::do_migration(&model_connector, migration);
ok = ok && Migration::do_migration(&model_connector, user);
/*
* do migration of ItemContent before Item
* Item::do_migration_to_3() requires that ItemContent should have new columns
*
*/
ok = ok && Migration::do_migration(&model_connector, item_content);
ok = ok && Migration::do_migration(&model_connector, item);
ok = ok && Migration::do_migration(&model_connector, group);
return ok;
}
bool App::TryToMakeDatabaseMigration()
{
if( config.db_make_migration_if_needed )
{
if( !DoDatabaseMigration() )
{
if( config.db_stop_if_migration_fails )
{
log << log1 << "App: database migration failed, stopping winix" << logend;
return false;
}
}
PluginRes res = plugin.Call(nullptr, WINIX_MAKE_DATABASE_MIGRATION);
if( config.db_stop_if_migration_fails && res.res_false > 0 )
{
log << log1 << "App: database migration of some plugins failed, stopping winix" << logend;
return false;
}
}
return true;
}
bool App::Init()
{
postgresql_connector.set_conn_param(config.db_database, config.db_user, config.db_pass);
postgresql_connector.set_logger(log);
postgresql_connector.set_log_queries(config.log_db_query);
postgresql_connector.wait_for_connection();
model_connector.set_flat_connector(json_connector);
model_connector.set_db_connector(postgresql_connector);
model_connector.set_logger(log);
model_connector.set_winix_config(&config);
model_connector.set_winix_request(&req);
model_connector.set_winix_logger(&log);
model_connector.set_winix_dirs(&system.dirs);
model_connector.set_winix_mounts(&system.mounts);
model_connector.set_winix_users(&system.users);
model_connector.set_winix_groups(&system.groups);
model_connector.set_winix_session_logger(nullptr); // will be set for each request
model_connector.set_winix_session(nullptr); // will be set for each request
model_connector.set_winix_locale(&TemplatesFunctions::locale);
model_connector.set_winix_session_manager(&session_manager);
model_connector.set_winix_time_zones(&system.time_zones);
model_connector.set_winix_pattern_cacher(&TemplatesFunctions::pattern_cacher);
if( !TryToMakeDatabaseMigration() )
return false;
db_conn.SetConnParam(config.db_database, config.db_user, config.db_pass);
db_conn.WaitForConnection();
db.LogQueries(config.log_db_query);
cur.request->Clear();
compress.set_dependency(&winix_base);
compress.Init();
system.Init();
functions.Init();
templates.Init(); // init templates after functions are created
// init notify after templates (it uses locales from templates)
system.notify.ReadTemplates();
session_manager.InitTmpSession();
session_manager.InitBanList();
session_manager.InitCookieEncoding();
session_manager.LoadSessions();
CreateStaticTree();
post_parser.set_dependency(&winix_model);
post_parser.LogValueSize(config.log_post_value_size);
// post_multi_parser has a pointer to the config
cookie_parser.set_dependency(&winix_model);
plugin.Call((Session*)0, WINIX_PLUGIN_INIT);
return true;
}
void App::Close()
{
{
Winix::Lock lock(synchro);
plugin.Call((Winix::Session*)0, WINIX_CLOSE);
session_manager.SaveSessions();
session_manager.DeleteSessions();
cur.request->Clear();
session_manager.UninitTmpSession();
functions.Finish();
// now all sessions are cleared
}
WaitForThreads();
// now all others threads are terminated
}
void App::BaseUrlRedirect(int code, bool add_subdomain)
{
system.PutUrlProto(cur.request->redirect_to);
if( add_subdomain && !cur.request->subdomain.empty() )
{
cur.request->redirect_to += cur.request->subdomain;
cur.request->redirect_to += '.';
}
cur.request->redirect_to += config.base_url;
cur.request->redirect_to += cur.request->env_request_uri;
// cur.request->env_request_uri should not be UrlEncoded because it contains slashes
cur.request->redirect_type = code;
}
bool App::BaseUrlRedirect()
{
plugin.Call((Session*)0, WINIX_BASE_URL_REDIRECT);
if( !cur.request->redirect_to.empty() )
return true;
if( !config.base_url_redirect )
return false;
if( config.base_url.empty() )
return false;
if( cur.request->method == Request::post )
return false;
if( config.base_url == cur.request->env_http_host )
return false;
BaseUrlRedirect(config.base_url_redirect_code, false);
log << log3 << "App: BaseUrlRedirect from: " << cur.request->env_http_host << logend;
return true;
}
void App::CheckIfNeedSSLredirect()
{
if( cur.request->method == Request::post )
{
// something comes via POST, don't do the redirect because you lose the date
return;
}
if( config.use_ssl )
{
if( config.use_ssl_only_for_logged_users )
{
bool function_need_ssl = (cur.request->function && cur.request->function->need_ssl);
if( cur.request->using_ssl )
{
if( !cur.session->puser && !function_need_ssl )
{
log << log3 << "App: this operation should NOT be used through SSL" << logend;
BaseUrlRedirect(config.use_ssl_redirect_code, true);
}
}
else
{
if( cur.session->puser || function_need_ssl )
{
log << log3 << "App: this operation should be used through SSL" << logend;
BaseUrlRedirect(config.use_ssl_redirect_code, true);
}
}
}
else
{
/*
* use ssl for everyone
*/
if( !cur.request->using_ssl )
{
log << log3 << "App: this operation should be used through SSL" << logend;
BaseUrlRedirect(config.use_ssl_redirect_code, true);
}
}
}
else
{
if( cur.request->using_ssl )
{
log << log3 << "App: this operation should NOT be used through SSL" << logend;
BaseUrlRedirect(config.use_ssl_redirect_code, true);
}
}
}
void App::SetLocale()
{
size_t locale_id;
if( cur.session->puser )
{
locale_id = cur.session->puser->locale_id;
if( !TemplatesFunctions::locale.HasLanguage(locale_id) )
locale_id = config.locale_default_id;
}
else
{
locale_id = config.locale_default_id;
}
TemplatesFunctions::locale.SetCurLang(locale_id);
}
bool App::CheckAccessFromPlugins()
{
PluginRes res = plugin.Call(WINIX_CHECK_PLUGIN_ACCESS);
if( res.res_false > 0 )
{
cur.request->status = WINIX_ERR_PERMISSION_DENIED;
log << log2 << "App: access prevented by a plugin" << logend;
return false;
}
return true;
}
void App::ProcessRequestThrow()
{
ReadRequest();
// when BaseUrlRedirect() return true we didn't have to set everything in cur.request->Read()
// in the future cur.request->Read() can be split and at the beginning only environment variables will be read
// and then BaseUrlRedirect() will be called (for performance)
if( !BaseUrlRedirect() )
{
if( cur.request->env_request_uri.size() <= WINIX_URL_MAX_SIZE )
{
functions.Parse(); // parsing directories, files, functions and parameters
if( cur.request->function )
{
cur.request->function->fun.set_connector(model_connector); // IMPROVEME may would be better to add set_connector() method to functions?
cur.request->function->fun.propagate_connector();
}
/*
* set global connector for now
* in the future each thread will have its own model_connector
*
* don't set connector for item_tab - it will be moved out from request
*/
cur.request->item.set_connector(model_connector);
if( !cur.request->dir_tab.empty() )
{
cur.mount = system.mounts.CalcCurMount();
cur.session = session_manager.PrepareSession();
model_connector.set_winix_session(cur.session);
functions.CheckFunctionAndSymlink(); // here a function can be changed
if( cur.request->function )
{
cur.request->function->fun.set_connector(model_connector);
cur.request->function->fun.propagate_connector();
}
cur.session = session_manager.CheckIfFunctionRequireSession();
model_connector.set_winix_session(cur.session);
SetLocale();
if( cur.session->new_session )
{
cur.session->plugin_data.Resize(plugin.Size());
plugin.Call(WINIX_SESSION_CREATED);
}
plugin.Call(WINIX_SESSION_CHANGED);
}
}
else
{
/*
* IMPROVE ME
* it will not have the root directory set
* so as a response only a blank page is shown
* (root directory is set in funcions.Parse())
*
* IMPROVE ME
* we can add a better return code (http status):
* http://www.ietf.org/rfc/rfc2616.txt
* "A server SHOULD return 414 (Request-URI Too Long) status if a URI is longer than the server can handle"
*
*/
cur.request->status = WINIX_ERR_PERMISSION_DENIED;
log << log1 << "App: the URL is too long: " << cur.request->env_request_uri.size() << logend;
}
if( cur.request->dir_tab.empty() )
{
log << log1 << "App: there is no a root dir (dir_tab is empty), adding a root dir" << logend;
Item * root_dir = system.dirs.GetRootDir();
if( root_dir )
{
cur.request->dir_tab.push_back(root_dir);
cur.request->last_item = cur.request->dir_tab.back();
cur.mount = system.mounts.CalcCurMount();
}
else
{
log << log1 << "App: oops, we do not have a root dir" << logend;
}
}
if( cur.mount->type != system.mounts.MountTypeStatic() )
Make();
}
SendAnswer();
}
void App::ProcessRequest()
{
try
{
cur.request->set_connector(model_connector);
model_connector.set_winix_request(cur.request);
cur.request->RequestStarts();
system.load_avg.StartRequest(cur.request);
log << log2 << config.log_delimiter << logend;
ProcessRequestThrow();
SaveSessionsIfNeeded();
cur.request->RequestEnds();
system.load_avg.StopRequest(cur.request);
LogRequestTime();
}
catch(const std::exception & e)
{
log << log1 << "App: there was an exception: std::exception: " << e.what() << logend;
}
catch(const Error & e)
{
log << log1 << "App: there was an exception: Error: " << e << logend;
}
catch(...)
{
log << log1 << "App: there was an unknown exception" << logend;
}
ClearAfterRequest();
}
void App::ClearAfterRequest()
{
try
{
plugin.Call(WINIX_END_REQUEST);
}
catch(...)
{
log << log1 << "App: an exception from a plugin when clearing after a request" << logend;
}
try
{
// simple operations which should not throw an exception
json_out_stream.clear();
templates.ClearAfterRequest();
cur.request->Clear();
cur.session->ClearAfterRequest();
cur.session = session_manager.GetTmpSession();
output_8bit.clear();
output_tmp_filtered_stream.clear();
compressed_output.clear();
//html_filtered.clear();
aheader_name.clear();
aheader_value.clear();
cur.mount = system.mounts.GetEmptyMount();
system.mounts.pmount = cur.mount; // IMPROVE ME system.mounts.pmount will be removed
// send_data_buf doesn't have to be cleared and it is better to not clear it (optimizing)
model_connector.set_winix_request(nullptr);
model_connector.set_winix_session(nullptr);
model_connector.set_winix_session_logger(nullptr);
cur.request->item.set_connector(nullptr); // it is needed?
log << logendrequest;
}
catch(...)
{
log << log1 << "App: an exception when clearing after a request" << logend;
}
}
void App::Start()
{
bool was_stop_signal = false;
{
Winix::Lock lock(synchro);
was_stop_signal = synchro.was_stop_signal;
}
while( !was_stop_signal && FCGX_Accept_r(&fcgi_request) == 0 )
{
Winix::Lock lock(synchro);
if( synchro.was_stop_signal )
{
was_stop_signal = true;
FCGX_Finish_r(&fcgi_request);
}
else
{
ProcessRequest();
}
}
}
void App::SaveSessionsIfNeeded()
{
time_t t = std::time(0);
if( last_sessions_save + 86400 > t )
return;
// saving once a day for safety
last_sessions_save = t;
session_manager.SaveSessions();
}
// !! IMPROVE ME change to a better name
void App::UseEzcGenerator()
{
// if( cur.request->page_generated || !cur.request->redirect_to.empty() || !cur.request->x_sendfile.empty() )
// return;
clock_gettime(CLOCK_REALTIME, &cur.request->timespec_ezc_engine_start);
templates.SetEzcParameters( cur.request->gen_trim_white,
cur.request->gen_skip_new_line,
cur.request->gen_use_special_chars);
templates.Generate();
clock_gettime(CLOCK_REALTIME, &cur.request->timespec_ezc_engine_stop);
timespec diff;
calculate_timespec_diff(cur.request->timespec_ezc_engine_start, cur.request->timespec_ezc_engine_stop, diff);
pt::TextStream str;
timespec_to_stream_with_unit(diff, str); // IMPROVEME in the future Log can be used directly
log << log3 << "App: ezc engine took: " << str << logend;
}
void App::CheckPostRedirect()
{
if( cur.request->method == Request::post )
{
if( cur.request->IsParam(L"postredirect") )
{
cur.request->redirect_to = cur.request->ParamValue(L"postredirect");
cur.request->redirect_type = 303;
}
else
if( cur.request->IsPostVar(L"postredirect") )
{
cur.request->redirect_to = cur.request->PostVar(L"postredirect");
cur.request->redirect_type = 303;
}
}
}
void App::AddDefaultModels()
{
if( cur.request->function && cur.request->function->register_default_models )
{
// may it would be better do not return cur.request by default?
cur.request->models.Add(L"request", cur.request);
if( cur.session && cur.session->puser )
{
cur.request->models.Add(L"user", *cur.session->puser);
}
if( cur.request->is_item )
{
cur.request->models.Add(L"item", cur.request->item);
}
}
}
// !! IMPROVE ME change to a better name
// may ProcessRequest()? but probably it is already defined...
// this method needs some refactoring
void App::Make()
{
if( cur.request->dir_tab.empty() )
{
log << log1 << "App: there is no a root dir (dir_tab is empty)" << logend;
return;
}
cur.request->PrepareAnswerType();
if( cur.session->ip_ban && cur.session->ip_ban->IsIPBanned() )
{
pt::Date date(cur.session->ip_ban->expires);
// IMPROVE ME there is no slog now
//slog << logerror << T("this_ip_is_banned_until") << ' ' << date << " UTC" << logend;
cur.request->status = WINIX_ERR_PERMISSION_DENIED;
}
// cur.request->status can be changed by function_parser
if( cur.request->status == WINIX_ERR_OK )
plugin.Call(WINIX_PREPARE_REQUEST);
// if( cur.request->status == WINIX_ERR_OK )
// functions.CheckFunctionAndSymlink();
CheckAccessFromPlugins();
// !! CHECK ME CheckFunctionAndSymlink can set redirect_to
// may it should be tested before calling CheckIfNeedSSLredirect?
CheckIfNeedSSLredirect();
if( !cur.request->redirect_to.empty() )
return;
AddDefaultModels();
if( cur.request->status == WINIX_ERR_OK )
functions.MakeFunction();
if( cur.session->spam_score > 0 )
log << log1 << "App: spam score: " << cur.session->spam_score << logend;
if( cur.request->IsParam(L"noredirect") )
cur.request->redirect_to.clear();
if( cur.request->status == WINIX_ERR_OK )
plugin.Call(WINIX_PROCESS_REQUEST);
CheckPostRedirect();
if( !cur.request->redirect_to.empty() )
return;
if( cur.request->dir_tab.empty() )
{
log << log1 << "App: there is no a root dir (dir_tab is empty -- after calling a function)" << logend;
return;
}
}
void App::LogEnvironmentVariables()
{
for(char ** e = fcgi_request.envp ; *e ; ++e )
log << log1 << "Env: " << *e << logend;
}
void App::LogEnvironmentHTTPVariables()
{
if( cur.request->headers_in.is_object() )
{
pt::Space::ObjectType::iterator i = cur.request->headers_in.value.value_object.begin();
for( ; i != cur.request->headers_in.value.value_object.end() ; ++i)
{
log << log1 << "HTTP Env: " << i->first << "=";
if( i->second->is_wstr() )
log << *i->second->get_wstr() << logend;
else
log << "(incorrect value type, expected wstr)" << logend;
}
}
}
void App::ParseAcceptHeader(const wchar_t * header_name, const std::wstring & env, std::vector<HeaderValue> & container, size_t max_len)
{
accept_base_parser.parse(env, container, max_len);
if( !container.empty() )
{
log << log3 << "App: " << header_name << " header consists of: ";
HeaderValue::log_values(container, log);
log << logend;
}
else
{
log << log3 << "App: there is no " << header_name << " header" << logend;
}
}
void App::ParseAcceptHeader()
{
ParseAcceptHeader(
Winix::Header::accept,
cur.request->env_http_accept,
cur.request->accept_mime_types,
config.request_max_accept_fields);
}
void App::ParseAcceptLanguageHeader()
{
ParseAcceptHeader(
Winix::Header::accept_language,
cur.request->env_http_accept_language,
cur.request->accept_languages,
config.request_max_accept_language_fields);
}
/*
* reading the request (without GET parameters in the URL)
*/
void App::ReadRequest()
{
ReadEnvVariables();
ReadEnvRemoteIP();
CheckRequestMethod();
CheckSSL();
SetSubdomain();
LogAccess();
ReadEnvHTTPVariables();
ReadPostVars();
if( config.log_env_variables )
LogEnvironmentVariables();
if( config.log_env_http_variables )
LogEnvironmentHTTPVariables();
ParseAcceptHeader();
ParseAcceptLanguageHeader();
accept_encoding_parser.ParseAndLog(cur.request->env_http_accept_encoding, log);
cookie_parser.Parse(cur.request->env_http_cookie, cur.request->cookie_tab);
CheckIE();
CheckKonqueror();
CheckHtmx();
if( cur.request->using_ssl )
log << log3 << "App: connection secure through SSL" << logend;
}
void App::SetEnv(const char * name, std::wstring & env)
{
const char * v = FCGX_GetParam(name, fcgi_request.envp);
if( v )
{
pt::utf8_to_wide(v, env);
}
}
/*
* IMPROVE ME take it from cur.request.headers_in?
*/
void App::ReadEnvVariables()
{
SetEnv("REQUEST_METHOD", cur.request->env_request_method);
SetEnv("REQUEST_URI", cur.request->env_request_uri);
SetEnv("FCGI_ROLE", cur.request->env_fcgi_role);
SetEnv("CONTENT_TYPE", cur.request->env_content_type);
SetEnv("HTTPS", cur.request->env_https);
SetEnv("HTTP_HOST", cur.request->env_http_host);
SetEnv("HTTP_USER_AGENT", cur.request->env_http_user_agent);
SetEnv("HTTP_COOKIE", cur.request->env_http_cookie);
SetEnv("HTTP_ACCEPT_ENCODING", cur.request->env_http_accept_encoding);
SetEnv("HTTP_ACCEPT", cur.request->env_http_accept);
SetEnv("HTTP_ACCEPT_LANGUAGE", cur.request->env_http_accept_language);
}
// reading from fastcgi env
void App::ReadEnvHTTPVariables()
{
const char http_prefix[] = "HTTP_";
size_t http_prefix_len = sizeof(http_prefix) / sizeof(char) - 1; // 1 means terminating null character
size_t http_headers_saved = 0;
for(char ** e = fcgi_request.envp ; *e && http_headers_saved < Request::MAX_INPUT_HEADERS ; ++e)
{
char * env = *e;
if( pt::is_substr_nc("HTTP_", env) )
{
env += http_prefix_len;
// cookies we have in a different table
if( !pt::is_substr_nc("COOKIE=", env) )
{
if( SaveEnvHTTPVariable(env) )
{
http_headers_saved += 1;
}
}
}
}
if( http_headers_saved == Request::MAX_INPUT_HEADERS )
{
log << log4 << "App: the maximum number of HTTP headers has been reached, skipping the rest" << logend;
}
}
/*
* headers in fcgi are in the form of name=value
*/
bool App::SaveEnvHTTPVariable(const char * env)
{
// CHECK ME may move to a better place? Request::INPUT_HEADER_VALUE_MAX_LENGTH is a high value
char header_name[Request::INPUT_HEADER_NAME_MAX_LENGTH + 1];
char header_value[Request::INPUT_HEADER_VALUE_MAX_LENGTH + 1];
size_t i = 0;
for( ; env[i] != 0 && env[i] != '=' && i < Request::INPUT_HEADER_NAME_MAX_LENGTH ; ++i)
{
header_name[i] = pt::to_lower(env[i]);
}
header_name[i] = 0;
if( env[i] != '=' )
{
// too long header name, skipping
log << log4 << "App: skipped HTTP header \"" << env << "\" because the header name is too long" << logend;
return false;
}
i += 1; // skipping '=' character
size_t h = 0;
for( ; env[i] != 0 && h < Request::INPUT_HEADER_VALUE_MAX_LENGTH ; ++i, ++h)
{
header_value[h] = env[i];
}
header_value[h] = 0;
if( env[i] != 0 )
{
// too long header value, skipping
log << log4 << "App: skipped HTTP header \"" << env << "\" because the header value is too long" << logend;
return false;
}
pt::utf8_to_wide(header_name, http_header_name);
pt::utf8_to_wide(header_value, http_header_value);
cur.request->headers_in.add(http_header_name, http_header_value);
http_header_name.clear();
http_header_value.clear();
return true;
}
void App::ReadEnvRemoteIP()
{
const char * v = nullptr;
if( config.check_proxy_ip_header )
{
http_header_name = L"HTTP_";
http_header_name += config.proxy_ip_header;
pt::to_upper_emplace(http_header_name);
pt::wide_to_utf8(http_header_name, http_header_8bit);
v = FCGX_GetParam(http_header_8bit.c_str(), fcgi_request.envp);
}
else
{
v = FCGX_GetParam("REMOTE_ADDR", fcgi_request.envp);
}
if( v )
{
cur.request->ip = (int)inet_addr(v);
pt::utf8_to_wide(v, cur.request->ip_str);
}
}
void App::CheckRequestMethod()
{
cur.request->method = Request::unknown_method;
if( !cur.request->env_request_method.empty() )
{
if( pt::to_lower(cur.request->env_request_method[0]) == 'g' )
cur.request->method = Request::get;
else
if( pt::to_lower(cur.request->env_request_method[0]) == 'p' )
cur.request->method = Request::post;
else
if( pt::to_lower(cur.request->env_request_method[0]) == 'h' )
cur.request->method = Request::head;
else
if( pt::to_lower(cur.request->env_request_method[0]) == 'd' )
cur.request->method = Request::delete_;
}
}
void App::CheckSSL()
{
// !! CHECK ME
// value "on" exists in lighttpd server
// make sure that for other servers is "on" too
if( config.assume_connection_is_through_ssl )
cur.request->using_ssl = true;
else
if( pt::is_equal_nc(cur.request->env_https.c_str(), L"on") )
cur.request->using_ssl = true;
}
void App::CheckHtmx()
{
// fastcgi will change the header to hx_request
cur.request->is_htmx_request = (cur.request->headers_in.has_key(L"HX-Request") || cur.request->headers_in.has_key(L"hx_request"));
if( cur.request->is_htmx_request && config.add_header_cache_no_store_in_htmx_request )
{
cur.request->out_headers.add(L"Cache-Control", L"no-store, max-age=0");
}
}
void App::SetSubdomain()
{
CreateSubdomain(config.base_url.c_str(), cur.request->env_http_host.c_str(), cur.request->subdomain);
}
void App::LogAccess()
{
log << log1;
log.PrintDate(cur.request->start_date);
log << ' '
<< cur.request->ip_str << ' '
<< cur.request->env_request_method << ' '
<< cur.request->env_http_host
<< cur.request->env_request_uri << ' '
<< cur.request->env_http_user_agent << logend;
if( !cur.request->subdomain.empty() )
log << log3 << "Subdomain: " << cur.request->subdomain << logend;
}
void App::ReadPostJson()
{
char buffer[1024];
const int buffer_len = sizeof(buffer) / sizeof(char) - 1;
int read_len;
post_buffer.clear();
post_buffer.reserve(1024 * 1024 * 5); // IMPROVEME add to config?
cur.request->is_postin_used = true;
do
{
// IMPROVE ME
// we can read to pt::TextBuffer and make a pt::JSONToSpaceParser::Parse(pt::TextBuffer &) method
read_len = FCGX_GetStr(buffer, buffer_len, fcgi_request.in);
if( read_len > 0 )
post_buffer.append(buffer, read_len);
}
while( read_len == buffer_len );
if( !post_buffer.empty() )
{
pt::SpaceParser::Status status = space_parser.parse_json(post_buffer.c_str(), cur.request->post_in);
post_buffer.clear();
if( status != pt::SpaceParser::ok )
{
log << log1 << "App: cannot parse the input stream as an JSON object";
if( status == pt::SpaceParser::syntax_error )
log << ", syntax error in line: " << space_parser.get_last_parsed_line() << logend;
cur.request->post_in.clear();
// return an error (http error of some kind?)
}
}
else
{
log << log3 << "App: input body empty (skipping parsing)" << logend;
}
}
void App::ReadPostVars()
{
// CHECKME
// what about errors during parsing input?
if( cur.request->method == Request::post || cur.request->method == Request::delete_ )
{
if( pt::is_substr_nc(L"multipart/form-data", cur.request->env_content_type.c_str()) )
{
log << log3 << "App: post content type: multipart/form-data" << logend;
post_multi_parser.Parse(fcgi_request.in, cur.request->post_tab, cur.request->post_file_tab);
}
else
if( pt::is_substr_nc(Winix::Header::application_json, cur.request->env_content_type.c_str()) )
{
log << log3 << "App: post content type: " << Winix::Header::application_json << logend;
ReadPostJson();
}
else
{
// IMPROVE ME may to check a correct content-type header?
post_parser.Parse(fcgi_request.in, cur.request->post_tab);
}
}
}
void App::CheckIE()
{
size_t msie = cur.request->env_http_user_agent.find(L"MSIE");
cur.request->browser_msie = (msie != std::wstring::npos);
}
void App::CheckKonqueror()
{
size_t kon = cur.request->env_http_user_agent.find(L"Konqueror");
cur.request->browser_konqueror = (kon != std::wstring::npos);
}
void App::PrepareSessionCookie()
{
if( !cur.session || cur.session->id==0 )
return;
if( config.session_cookie_encode )
{
if( !session_manager.EncodeSessionId(cur.session->id, cur.session->id_index, cookie_id_string) )
Toa(cur.session->id, cookie_id_string);
}
else
{
Toa(cur.session->id, cookie_id_string);
}
if( !cur.session->puser || !cur.session->remember_me )
{
cur.request->AddCookie(config.http_session_id_name, cookie_id_string);
}
else
{
pt::Date expires = cur.request->start_time + config.session_remember_max_idle;
cur.request->AddCookie(config.http_session_id_name, cookie_id_string, expires);
}
}
bool App::AddHeader(const wchar_t * name, const wchar_t * value)
{
if( !cur.request->out_headers.has_key(name) )
{
cur.request->out_headers.add(name, value);
return true;
}
return false;
}
bool App::AddHeader(const std::wstring & name, const std::wstring & value)
{
if( !cur.request->out_headers.has_key(name) )
{
cur.request->out_headers.add(name, value);
return true;
}
return false;
}
bool App::AddHeader(const wchar_t * name, const pt::WTextStream & value)
{
if( !cur.request->out_headers.has_key(name) )
{
cur.request->out_headers.add_stream(name, value);
return true;
}
return false;
}
bool App::AddHeader(const std::wstring & name, const pt::WTextStream & value)
{
if( !cur.request->out_headers.has_key(name) )
{
cur.request->out_headers.add_stream(name, value);
return true;
}
return false;
}
bool App::PrepareHeadersStaticCreateResource(pt::WTextStream & out_path)
{
size_t i = 0;
Item * dir = system.dirs.GetDir(system.mounts.pmount->dir_id);
if( !dir )
{
log << log1 << "App: cannot find the mount directory" << logend;
return false;
}
size_t how_many_dirs = system.dirs.DirLevel(dir->id);
const wchar_t * path = SkipDirs(cur.request->env_request_uri.c_str(), how_many_dirs);
// the path begins with a slash only if how_many_dirs is zero
while( *path == '/' )
path += 1;
while( path[i]!=0 && path[i]!='?' && path[i]!='#' )
++i;
if( i > 0 )
out_path.write(path, i);
return true;
}
void App::PrepareHeadersStatic()
{
if( PathHasUpDir(cur.request->env_request_uri) )
{
log << log1 << "App: incorrect path for a static file" << logend;
PrepareHeadersForbidden();
return;
}
const std::wstring & index_str = system.mounts.pmount->FirstArg(system.mounts.MountParStatic());
size_t index = Toi(index_str);
if( index >= config.static_dirs.size() )
{
log << log1 << "App: static dir with index " << index << " is not defined in the config" << logend;
PrepareHeadersForbidden();
return;
}
pt::WTextStream path;
path << config.static_dirs[index] << L"/";
if( !PrepareHeadersStaticCreateResource(path) )
{
PrepareHeadersForbidden();
return;
}
AddHeader(L"Status", L"200 OK");
/*
* FIX ME now we can send full path (apache, lighttpd) and relative path (nginx)
* but this feature for mounting static content probably will be removed
*/
if( AddHeader(config.send_file_header, path) )
log << log2 << "App: sending a file from a static mountpoint: " << path << logend;
}
void App::PrepareHeaderContentType()
{
if( !cur.request->out_headers.has_key(Winix::Header::content_type) )
{
if( cur.request->container_type == Request::ContainerType::container_json )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_json_utf8);
}
else
if( cur.request->container_type == Request::ContainerType::container_xml )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_xml_utf8);
}
else
if( cur.request->container_type == Request::ContainerType::container_csv )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::text_csv_utf8);
}
else
if( cur.request->container_type == Request::ContainerType::container_raw )
{
if( cur.request->send_bin_stream )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_octet_stream);
}
else
{
switch( config.content_type_header )
{
case 1:
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_xhtml_xml_utf8);
break;
case 2:
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_xml_utf8);
break;
case 0:
default:
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::text_html_utf8);
}
}
}
}
}
void App::PrepareHeadersForbidden()
{
AddHeader(L"Status", L"403 Forbidden");
PrepareHeaderContentType();
}
void App::PrepareHeadersRedirect()
{
switch(cur.request->redirect_type)
{
case 300:
AddHeader(L"Status", L"300 Multiple Choices");
break;
case 301:
AddHeader(L"Status", L"301 Moved Permanently");
break;
case 302:
AddHeader(L"Status", L"302 Found");
break;
case 307:
AddHeader(L"Status", L"307 Temporary Redirect");
break;
case 303:
default:
AddHeader(L"Status", L"303 See Other");
break;
}
AddHeader(L"Location", cur.request->redirect_to);
log << log2 << "App: redirect to: " << cur.request->redirect_to << logend;
}
void App::PrepareHeadersSendFile()
{
AddHeader(L"Status", L"200 OK");
if( AddHeader(config.send_file_header, cur.request->x_sendfile) )
log << log2 << "App: sending file: " << cur.request->x_sendfile << logend;
}
void App::PrepareHeadersCompression(int compress_encoding)
{
if( compress_encoding == 0 || compress_encoding == 1 )
AddHeader(L"Content-Encoding", L"deflate");
else
AddHeader(L"Content-Encoding", L"gzip");
}
void App::PrepareHeadersNormal(Header header, size_t output_size)
{
switch( header )
{
case h_404:
AddHeader(L"Status", L"404 Not Found");
PrepareHeaderContentType();
break;
case h_403:
PrepareHeadersForbidden();
break;
default:
AddHeader(L"Status", L"200 OK");
PrepareHeaderContentType();
}
if( output_size != static_cast<size_t>(-1) )
{
pt::WTextStream buf;
buf << output_size;
AddHeader(L"Content-Length", buf);
}
}
// we can improve SendHeaders and SendCookies methods by checking
// whether there is a new line character in either a name or a value
// and if such character exists and is being sent to the client it breaks the http headers and content
// and if compression is enabled the client's browser will not be able to decompress the stream
void App::SendHeaders()
{
pt::Space::ObjectType::iterator i;
pt::Space & headers = cur.request->out_headers;
if( headers.is_object() )
{
plugin.Call(WINIX_PREPARE_TO_SEND_HTTP_HEADERS, &headers);
for(i=headers.value.value_object.begin() ; i != headers.value.value_object.end() ; ++i)
{
if( i->second->is_wstr() )
{
pt::wide_to_utf8(i->first, aheader_name);
pt::wide_to_utf8(*i->second->get_wstr(), aheader_value);
FCGX_PutS(aheader_name.c_str(), fcgi_request.out);
FCGX_PutS(": ", fcgi_request.out);
FCGX_PutS(aheader_value.c_str(), fcgi_request.out);
FCGX_PutS("\r\n", fcgi_request.out);
if( config.log_http_answer_headers )
log << log1 << "HTTP Header: " << aheader_name << ": " << aheader_value << logend;
}
else
{
log << log2 << "Skipping HTTP Header: " << i->first << " - it's not a wstr" << logend;
}
}
}
}
void App::SendCookies()
{
pt::Space::ObjectType::iterator i;
pt::Space & cookies = cur.request->out_cookies;
if( cookies.is_object() )
{
plugin.Call(WINIX_PREPARE_TO_SEND_HTTP_COOKIES, &cookies);
for(i=cookies.value.value_object.begin() ; i != cookies.value.value_object.end() ; ++i)
{
if( i->second->is_wstr() )
{
pt::wide_to_utf8(i->first, aheader_name);
pt::wide_to_utf8(*i->second->get_wstr(), aheader_value);
FCGX_PutS("Set-Cookie: ", fcgi_request.out);
FCGX_PutS(aheader_name.c_str(), fcgi_request.out);
FCGX_PutS("=", fcgi_request.out);
FCGX_PutS(aheader_value.c_str(), fcgi_request.out);
FCGX_PutS("\r\n", fcgi_request.out);
if( config.log_http_answer_headers )
log << log1 << "HTTP Header: Set-Cookie: " << aheader_name << "=" << aheader_value << logend;
}
else
{
log << log2 << "Skipping Cookie: " << i->first << " - it's not a wstr" << logend;
}
}
}
}
void App::PrepareHeaders(bool compressing, int compress_encoding, Header header, size_t output_size)
{
PrepareSessionCookie();
if( cur.request->send_as_attachment )
AddHeader(L"Content-Disposition", L"attachment");
//if( !cur.request->redirect_to.empty() && !cur.request->return_json )
if( !cur.request->redirect_to.empty() )
{
PrepareHeadersRedirect();
}
else
if( system.mounts.pmount->type == system.mounts.MountTypeStatic() )
{
PrepareHeadersStatic();
}
else
if( !cur.request->x_sendfile.empty() )
{
PrepareHeadersSendFile();
}
else
{
PrepareHeadersNormal(header, output_size);
}
if( compressing )
PrepareHeadersCompression(compress_encoding);
}
int App::SelectDeflateVersion()
{
if( cur.request->browser_msie )
return 0; // raw deflate
else
return 1; // deflate
}
void App::SelectCompression(size_t source_len, bool & compression_allowed, int & compression_encoding)
{
compression_allowed = false;
compression_encoding = 0;
if( config.compression &&
cur.request->redirect_to.empty() &&
cur.request->x_sendfile.empty() &&
!cur.request->browser_konqueror && /* !! sprawdzic czy Konqueror bedzie obslugiwal raw deflate */
source_len >= config.compression_page_min_size )
{
if( config.compression_encoding == 1 || config.compression_encoding == 10 )
{
if( accept_encoding_parser.AcceptDeflate() )
{
compression_allowed = true;
compression_encoding = SelectDeflateVersion();
}
else
if( config.compression_encoding == 10 && accept_encoding_parser.AcceptGzip() )
{
compression_allowed = true;
compression_encoding = 2; // gzip
}
}
if( config.compression_encoding == 2 || config.compression_encoding == 20 )
{
if( accept_encoding_parser.AcceptGzip() )
{
compression_allowed = true;
compression_encoding = 2; // gzip
}
else
if( config.compression_encoding == 20 && accept_encoding_parser.AcceptDeflate() )
{
compression_allowed = true;
compression_encoding = SelectDeflateVersion();
}
}
}
}
bool App::CanSendContent()
{
if( !cur.request->x_sendfile.empty() )
{
// if there is a file to send then we do not send a content
return false;
}
// if( cur.request->return_json )
// {
// // if there is a redirect flag then it will be put to info struct
// return true;
// }
if( !cur.request->redirect_to.empty() )
{
// if there is a redirect and no json is requred then we do not send the content
return false;
}
/*
we don't have to check the HEAD method
the server (lighttpd) doesn't send the body of its own
*/
if( cur.request->method == Request::head )
return false;
return true;
}
App::Header App::GetHTTPStatusCode()
{
Error status = cur.request->status;
Header header = h_200;
if( status == WINIX_ERR_NO_ITEM || status == WINIX_ERR_NO_FUNCTION || status == WINIX_ERR_UNKNOWN_PARAM )
{
header = h_404;
log << log2 << "App: http response: 404 Not Found" << logend;
}
if( status == WINIX_ERR_PERMISSION_DENIED || status == WINIX_ERR_CANT_CHANGE_USER || status == WINIX_ERR_CANT_CHANGE_GROUP )
{
header = h_403;
log << log2 << "App: http response: 403 Forbidden" << logend;
}
if( cur.request->use_200_status_for_not_found_and_permission_denied && (header == h_404 || header == h_403) )
{
log << log3 << "App: changing the http response to: 200 OK" << logend;
header = h_200;
}
return header;
}
bool App::IsRequestedFrame()
{
if( !config.request_frame_parameter.empty() )
{
return cur.request->ParamValuep(config.request_frame_parameter);
}
return false;
}
// IMPROVEME
// we can send directly from BinaryPage without copying to a temporary buffer
// (but there is no an interface in BinaryPage yet)
void App::SendData(const BinaryPage & page, FCGX_Stream * out)
{
const size_t buf_size = 4096;
if( send_data_buf.size() != buf_size )
send_data_buf.resize(buf_size);
BinaryPage::const_iterator i = page.begin();
BinaryPage::const_iterator end = page.end();
while( i != end )
{
size_t s = 0;
for( ; i != end && s < buf_size ; ++i, ++s)
send_data_buf[s] = *i;
if( s > 0 )
FCGX_PutStr(send_data_buf.c_str(), s, out);
}
}
void App::SendAnswer()
{
output_8bit.clear();
compressed_output.clear();
// may use CanSendContent() method?
// what about method HEAD?
if( !cur.request->redirect_to.empty() || !cur.request->x_sendfile.empty() )
{
Send8bitOutput(output_8bit); // send empty content
return;
}
plugin.Call(WINIX_CONTENT_MAKE);
if( cur.request->use_ezc_engine )
{
UseEzcGenerator();
}
if( cur.request->container_type == Request::ContainerType::container_raw )
{
PrepareRawAnswer();
}
else
if( cur.request->container_type == Request::ContainerType::container_json )
{
PrepareJsonAnswer();
}
else
if( cur.request->container_type == Request::ContainerType::container_xml )
{
PrepareXmlAnswer();
}
else
if( cur.request->container_type == Request::ContainerType::container_csv )
{
PrepareCsvAnswer();
}
Send8bitOutput(output_8bit);
}
void App::PrepareRawAnswer()
{
if( cur.request->send_bin_stream )
{
Send8bitOutput(cur.request->out_bin_stream);
}
else
if( cur.request->send_main_stream )
{
FilterHtmlIfNeeded(cur.request->out_main_stream.get_buffer(), output_8bit, false);
}
else
if( cur.request->send_all_frames )
{
SerializeAllFrames();
}
else
if( !cur.request->send_frames.empty() )
{
SerializeSpecificFrames();
}
}
void App::PrepareJsonAnswer()
{
output_8bit << '{';
PrepareContenerizedAnswer();
output_8bit << '}';
}
void App::PrepareXmlAnswer()
{
output_8bit << '<';
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
PrepareContenerizedAnswer();
output_8bit << "</";
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
}
void App::PrepareCsvAnswer()
{
PrepareContenerizedAnswer();
}
void App::PrepareContenerizedAnswer()
{
bool put_separator = false;
if( cur.request->serialize_models )
{
SerializeModels();
put_separator = true;
}
if( cur.request->send_bin_stream )
{
PutSeparatorIfNeeded(put_separator);
// IMPLEMENT ME serialize binary stream as base64 and put in 'bin_stream' field
pt::WTextStream str;
str << "NOT IMPLEMENTED YET";
SerializeStream(str, config.bin_stream_field.c_str());
put_separator = true;
}
if( cur.request->send_main_stream )
{
PutSeparatorIfNeeded(put_separator);
SerializeStream(cur.request->out_main_stream.get_buffer(), config.main_stream_field.c_str());
put_separator = true;
}
if( cur.request->send_all_frames || !cur.request->send_frames.empty() )
{
PutSeparatorIfNeeded(put_separator);
SerializeFieldJson(config.ezc_frames_field.c_str());
output_8bit << "{";
if( cur.request->send_all_frames )
{
SerializeAllFrames();
}
else
if( !cur.request->send_frames.empty() )
{
SerializeSpecificFrames();
}
output_8bit << "}";
put_separator = true;
}
}
void App::PutSeparatorIfNeeded(bool put_separator)
{
if( put_separator )
{
switch( cur.request->container_type )
{
case Request::ContainerType::container_json:
output_8bit << ",";
break;
case Request::ContainerType::container_xml:
break;
case Request::ContainerType::container_csv:
output_8bit << ";";
break;
case Request::ContainerType::container_raw:
default:
break;
}
}
}
void App::SerializeFieldJson(const wchar_t * field_name)
{
if( field_name )
{
output_8bit << '"';
pt::esc_to_json(field_name, output_8bit);
output_8bit << "\":";
}
}
void App::SerializeStream(const pt::WTextStream & input_stream, const wchar_t * field_name)
{
switch( cur.request->container_type )
{
case Request::ContainerType::container_json:
SerializeStreamJson(input_stream, field_name);
break;
case Request::ContainerType::container_xml:
SerializeStreamXml(input_stream, field_name);
break;
case Request::ContainerType::container_csv:
SerializeStreamCsv(input_stream, field_name);
break;
case Request::ContainerType::container_raw:
default:
FilterHtmlIfNeeded(input_stream, output_8bit, false);
break;
}
}
void App::SerializeStreamJson(const pt::WTextStream & input_stream, const wchar_t * field_name)
{
SerializeFieldJson(field_name);
output_8bit << '"';
if( config.html_filter && cur.request->use_html_filter )
{
TemplatesFunctions::html_filter.filter(input_stream, output_tmp_filtered_stream, true);
pt::esc_to_json(output_tmp_filtered_stream, output_8bit);
}
else
{
pt::esc_to_json(input_stream, output_8bit);
}
output_8bit << '"';
}
void App::SerializeStreamXml(const pt::WTextStream & input_stream, const wchar_t * field_name)
{
if( field_name )
{
output_8bit << '<';
pt::esc_to_xml(field_name, output_8bit);
output_8bit << '>';
}
if( config.html_filter && cur.request->use_html_filter )
{
TemplatesFunctions::html_filter.filter(input_stream, output_tmp_filtered_stream, true);
pt::esc_to_xml(output_tmp_filtered_stream, output_8bit);
}
else
{
pt::esc_to_xml(input_stream, output_8bit);
}
if( field_name )
{
output_8bit << "</";
pt::esc_to_xml(field_name, output_8bit);
output_8bit << '>';
}
}
void App::SerializeStreamCsv(const pt::WTextStream & input_stream, const wchar_t * field_name)
{
if( field_name )
{
output_8bit << '"';
pt::esc_to_csv(field_name, output_8bit);
output_8bit << "\";";
}
output_8bit << '"';
if( config.html_filter && cur.request->use_html_filter )
{
TemplatesFunctions::html_filter.filter(input_stream, output_tmp_filtered_stream, true);
pt::esc_to_csv(output_tmp_filtered_stream, output_8bit);
}
else
{
pt::esc_to_csv(input_stream, output_8bit);
}
output_8bit << "\";\n";
}
void App::SerializeAllFrames()
{
auto i = cur.request->out_streams.streams_map.begin();
bool is_first = true;
for( ; i != cur.request->out_streams.streams_map.end() ; ++i)
{
if( cur.request->container_type == Request::ContainerType::container_json && !is_first )
{
output_8bit << ',';
}
if( cur.request->container_type == Request::ContainerType::container_xml && i->first.empty() )
{
log << log2 << "App: I cannot serialize a frame with an empty name to xml (frame skipped)" << logend;
}
else
{
SerializeStream(i->second->get_buffer(), i->first.c_str());
}
is_first = false;
}
}
void App::SerializeSpecificFrames()
{
bool is_first = true;
for(std::wstring & frame: cur.request->send_frames)
{
auto i = cur.request->out_streams.streams_map.find(frame);
if( i != cur.request->out_streams.streams_map.end() )
{
if( cur.request->container_type == Request::ContainerType::container_json && !is_first )
{
output_8bit << ',';
}
SerializeStream(i->second->get_buffer(), frame.c_str());
is_first = false;
}
else
{
log << log2 << "App: there is no such a frame: " << frame << logend;
}
}
}
void App::SerializeModels()
{
Ezc::Models::ModelsMap models_map = cur.request->models.GetMap();
auto i = models_map.begin();
bool is_first = true;
for( ; i != models_map.end() ; ++i)
{
if( cur.request->container_type == Request::ContainerType::container_json && !is_first )
{
output_8bit << ',';
}
if( cur.request->container_type == Request::ContainerType::container_xml && i->first.empty() )
{
log << log2 << "App: I cannot serialize a model with an empty name to xml (model skipped)" << logend;
}
else
{
SerializeModel(i->second, i->first.c_str());
}
is_first = false;
}
}
void App::SerializeModel(morm::Wrapper & wrapper, const wchar_t * field_name)
{
switch( cur.request->container_type )
{
case Request::ContainerType::container_json:
SerializeModelJson(wrapper, field_name);
break;
case Request::ContainerType::container_xml:
SerializeModelXml(wrapper, field_name);
break;
case Request::ContainerType::container_csv:
SerializeModelCsv(wrapper, field_name);
break;
case Request::ContainerType::container_raw:
default:
break;
}
}
void App::SerializeModelJson(morm::Wrapper & wrapper, const wchar_t * field_name)
{
SerializeFieldJson(field_name);
if( wrapper.model )
{
serialized_model.clear();
wrapper.model->set_connector(model_connector);
wrapper.model->to_text(serialized_model);
output_8bit << serialized_model;
}
if( wrapper.date )
{
output_8bit << '"';
wrapper.date->SerializeISO(output_8bit);
output_8bit << '"';
}
if( wrapper.space_wrapper )
{
wrapper.space_wrapper->get_space()->serialize_to_json_stream(output_8bit, false);
}
if( wrapper.model_container_wrapper )
{
wrapper.model_container_wrapper->set_iterator_at_first_model();
bool is_first = true;
output_8bit << '[';
while( wrapper.model_container_wrapper->is_iterator_correct() )
{
if( !is_first )
output_8bit << ',';
morm::Model * model = wrapper.model_container_wrapper->get_model();
serialized_model.clear();
model->set_connector(model_connector);
model->to_text(serialized_model);
output_8bit << serialized_model;
wrapper.model_container_wrapper->increment_iterator();
is_first = false;
}
output_8bit << ']';
}
}
void App::SerializeModelXml(morm::Wrapper & wrapper, const wchar_t * field_name)
{
// IMPROVEME
log << log2 << "App: serializing models to xml not implemented yet" << logend;
}
void App::SerializeModelCsv(morm::Wrapper & wrapper, const wchar_t * field_name)
{
// IMPROVEME
log << log2 << "App: serializing models to csv not implemented yet" << logend;
}
// IMPROVEME
// gime me a better name
void App::FilterHtmlIfNeeded(const pt::WTextStream & input_stream, BinaryPage & output, bool clear_stream)
{
if( config.html_filter && cur.request->use_html_filter )
{
TemplatesFunctions::html_filter.filter(input_stream, output, clear_stream);
}
else
{
pt::wide_stream_to_utf8(input_stream, output, clear_stream);
}
}
void App::Send8bitOutput(BinaryPage & output)
{
bool compressing = false;
int compress_encoding = 0;
Header header = GetHTTPStatusCode();
size_t output_size = 0;
SelectCompression(output.size(), compressing, compress_encoding);
if( config.log_server_answer )
{
log << log1 << "App: the server's answer is:\n" << output << "\nApp: end of the server's answer" << logend;
}
if( compressing )
{
compress.Compressing(output, compressed_output, compress_encoding);
output_size = compressed_output.size();
}
else
{
output_size = output.size();
}
PrepareHeaders(compressing, compress_encoding, header, output_size);
SendHeaders();
SendCookies();
FCGX_PutS("\r\n", fcgi_request.out);
if( CanSendContent() )
{
if( compressing )
SendData(compressed_output, fcgi_request.out);
else
SendData(output, fcgi_request.out);
}
}
void App::LogUser(const char * msg, uid_t id)
{
log << log3 << msg << " ";
passwd * p = getpwuid(id);
if( p )
log << p->pw_name;
else
log << (int)id;
log << logend;
}
void App::LogGroup(const char * msg, gid_t id, bool put_logend)
{
log << log3 << msg << " ";
group * g = getgrgid(id);
if( g )
log << g->gr_name;
else
log << (int)id;
if( put_logend )
log << logend;
}
void App::LogUsers()
{
uid_t eid, rid;
eid = geteuid();
rid = getuid();
if( eid == rid )
{
LogUser("App: effective/real user:", eid);
}
else
{
LogUser("App: effective user:", eid);
LogUser("App: real user:", rid);
}
}
void App::LogEffectiveGroups(std::vector<gid_t> & tab)
{
log << log3 << "App: effective groups:";
for(size_t i=0 ; i<tab.size() ; ++i)
{
bool was_printed = false;
for(size_t x=0 ; x<i ; ++x)
{
if( tab[i] == tab[x] )
{
was_printed = true;
break;
}
}
if( !was_printed )
LogGroup("", tab[i], false);
}
log << logend;
}
void App::LogGroups()
{
std::vector<gid_t> tab;
gid_t rgid;
int len;
rgid = getgid();
len = getgroups(0, 0);
if( len <= 0 )
{
log << log3 << "App: I can't read how many groups there are" << logend;
return;
}
tab.resize(len);
len = getgroups(len, &(tab[0]));
if( len == -1 )
{
log << log3 << "App: I can't read groups" << logend;
return;
}
if( len == 1 && rgid == tab[0] )
{
LogGroup("App: effective/real group:", rgid);
}
else
{
tab.resize(len);
LogEffectiveGroups(tab);
LogGroup("App: real group:", rgid);
}
}
void App::LogUserGroups()
{
LogUsers();
LogGroups();
log << log3 << "base_url: " << config.base_url << logend;
}
void App::LogRequestTime()
{
pt::TextStream str;
timespec_to_stream_with_unit(cur.request->timespec_req_diff, str);
log << log2 << "App: request took: " << str << logend;
}
bool App::DropPrivileges(char * user, char * group)
{
if( !wide_to_utf8(config.user, user, WINIX_OS_USERNAME_SIZE) )
return false;
if( !wide_to_utf8(config.group, group, WINIX_OS_USERNAME_SIZE) )
return false;
return true;
}
bool App::DropPrivileges(const char * user, uid_t uid, gid_t gid, bool additional_groups)
{
if( additional_groups )
{
if( initgroups(user, gid) < 0 )
{
log << log1 << "App: I can't init groups for a user: " << user << logend;
return false;
}
}
else
{
if( setgroups(1, &gid) < 0 )
{
log << log1 << "App: I can't init groups for user: " << user << logend;
return false;
}