winix/core/app.cpp

707 lines
15 KiB
C++
Raw Normal View History

/*
* This file is a part of Winix
* and is not publicly distributed
*
* Copyright (c) 2010, Tomasz Sowa
* All rights reserved.
*
*/
#include "app.h"
#include "plugin.h"
#include "misc.h"
#include "functions/functions.h"
App::App()
{
stdout_is_closed = false;
last_sessions_save = time(0);
plugin.SetDb(&db);
plugin.SetConfig(&config);
plugin.SetRequest(&request);
plugin.SetSystem(&system);
plugin.SetFunctions(&functions);
plugin.SetTemplates(&templates);
plugin.SetSessionManager(&session_manager);
request.SetConfig(&config);
functions.SetConfig(&config);
functions.SetRequest(&request);
functions.SetDb(&db);
functions.SetSystem(&system);
functions.SetTemplates(&templates);
functions.SetNotify(&notify);
system.SetConfig(&config);
system.SetRequest(&request);
system.SetDb(&db);
templates_notify.SetConfig(&config);
notify.SetRequest(&request);
notify.SetConfig(&config);
notify.SetSystem(&system);
notify.SetTemplatesNotify(&templates_notify);
templates.SetConfig(&config);
templates.SetRequest(&request);
templates.SetDb(&db);
templates.SetSystem(&system);
templates.SetFunctions(&functions);
templates.SetSessionManager(&session_manager);
session_manager.SetLastContainer(&system.users.last);
session_manager.SetConfig(&config);
session_manager.SetRequest(&request);
session_manager.SetSystem(&system);
post_multi_parser.SetConfig(&config);
}
bool App::CreateFCGISocket()
{
const char * sock = config.fcgi_socket.c_str();
unlink(sock);
int s = FCGX_OpenSocket(sock, 10);
if( s < 0 )
{
log << log1 << "An error during creating a socket" << logend;
return false;
}
chmod(sock, config.fcgi_socket_chmod);
passwd * pw = getpwnam(config.fcgi_socket_user.c_str());
if( !pw )
{
log << log1 << "There is no user: " << config.fcgi_socket_user << logend;
return false;
}
group * gr = getgrnam(config.fcgi_socket_group.c_str());
if( !gr )
{
log << log1 << "There is no group: " << config.fcgi_socket_group << logend;
return false;
}
chown(sock, pw->pw_uid, gr->gr_gid);
if( setuid(pw->pw_uid) < 0 )
{
log << log1 << "I can't change the user into: " << config.fcgi_socket_user << logend;
return false;
}
/*
if( setgid(gr->gr_gid) < 0 )
{
int e = errno;
log << log1 << "I can't change the group into: " << config.fcgi_socket_group << " " << gr->gr_gid << logend;
log << log1 << "errno: " << e << logend;
return false;
}
*/
dup2(s, 0);
return true;
}
bool App::Init()
{
if( !CreateFCGISocket() )
return false;
request.Clear();
compress.Init();
system.Init();
functions.Create();
// !! teraz mamy dwa katalogi z templetami
// !! o co chodzilo?
if( !notify.Init() )
return false;
// init templates after functions are created
templates.ReadIndexFileNames();
templates.ReadTemplates();
templates.CreateFunctions();
session_manager.LoadSessions();
return true;
}
void App::Close()
{
session_manager.SaveSessions();
session_manager.DeleteAllPluginsData();
}
bool App::BaseUrlRedirect()
{
if( request.role == Request::responder )
{
if( config.base_url_http_host.empty() )
return false;
if( config.base_url_http_host == request.env_http_host )
return false;
request.redirect_to = config.base_url + request.env_request_uri;
}
else
{
// authorizer
if( config.base_url_auth_http_host.empty() )
return false;
if( config.base_url_auth_http_host == request.env_http_host )
return false;
request.redirect_to = config.base_url_auth + request.env_request_uri;
}
log << log3 << "RC: BaseUrlRedirect from: " << request.env_http_host << logend;
return true;
}
void App::ProcessRequestThrow()
{
ReadRequest();
// when BaseUrlRedirect() return true we didn't have to set everything in request.Read()
// in the future 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() )
{
session_manager.DeleteOldSessions();
session_manager.SetSession(); // set request.session as well
// !! tutaj dodac to ustawianie request.session
functions.Parse();
system.mounts.CalcCurMount();
Make();
}
SendAnswer();
notify.ItemChanged(request.notify_code);
}
void App::ProcessRequest()
{
try
{
ProcessRequestThrow();
}
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;
}
}
void App::Start()
{
while( FCGX_Accept(&request.in, &request.out, &request.err, &request.env) == 0 )
{
system.load_avg.StartRequest();
log << log2 << config.log_delimiter << logend;
ProcessRequest();
SaveSessionsIfNeeded();
if( request.function )
request.function->Clear();
request.Clear();
system.load_avg.StopRequest();
log << logsave;
}
}
void App::SaveSessionsIfNeeded()
{
time_t t = time(0);
if( last_sessions_save + 86400 > t )
return;
// saving once a day for safety
last_sessions_save = t;
session_manager.SaveSessions();
}
// !! zmienic na lepsza nazwe
void App::MakePage()
{
bool sent = false;
if( !request.redirect_to.empty() || !request.x_sendfile.empty() )
return;
if( request.is_item && request.item.auth == Item::auth_none &&
request.item.content_type == Item::ct_raw && request.status == WINIX_ERR_OK && request.function )
{
if( request.function->fun.url == "cat" )
{
request.page << request.item.content;
sent = true;
}
else
if( request.function->fun.url == "run" )
{
templates.GenerateRunRaw();
sent = true;
}
}
if( !sent )
{
templates.Generate();
}
}
void App::Make()
{
if( request.dir_tab.empty() )
{
log << log1 << "Content: there is no a root dir (dir_tab is empty)" << logend;
return;
}
// request.status can be changed by function_parser
if( request.status == WINIX_ERR_OK )
{
if( system.DirsHaveReadExecPerm() )
{
if( request.method == Request::post )
functions.MakePost();
if( request.redirect_to.empty() && request.status == WINIX_ERR_OK )
functions.MakeGet();
}
else
request.status = WINIX_ERR_PERMISSION_DENIED;
}
if( request.session->spam_score > 0 )
log << log1 << "App: spam score: " << request.session->spam_score << logend;
if( request.IsParam("noredirect") )
request.redirect_to.clear();
if( !request.redirect_to.empty() )
return;
if( request.dir_tab.empty() )
{
log << log1 << "App: there is no a root dir (dir_tab is empty -- after calling a function)" << logend;
return;
}
plugin.Call(WINIX_CONTENT_MAKE);
MakePage();
if( config.debug_info )
{
// !! dodac inne informacje (get, post, itp)
// jesli jest debug_info wlaczone to nie robic przekierowan
request.PrintGetTab();
request.PrintEnv();
}
}
void App::ReadRequest()
{
ReadEnvVariables();
CheckRequestMethod();
CheckFCGIRole();
LogAccess();
ReadGetPostVars();
cookie_parser.Parse(request.env_http_cookie, request.cookie_tab);
accept_encoding_parser.Parse(request.env_http_accept_encoding);
CheckIE();
CheckKonqueror();
if( request.role == Request::authorizer )
log << log3 << "Request: fast cgi role: authorizer" << logend;
}
void App::SetEnv(const char * & env, const char * name)
{
const char * v = FCGX_GetParam(name, request.env);
if( v )
env = v;
// by default env is set to an empty string (in request.Clear() method)
}
void App::ReadEnvVariables()
{
// we store that values because FCGX_GetParam has O(n) complexity
// with this variables (env_*) we have O(1)
SetEnv(request.env_request_method, "REQUEST_METHOD");
SetEnv(request.env_request_uri, "REQUEST_URI");
SetEnv(request.env_http_cookie, "HTTP_COOKIE");
SetEnv(request.env_remote_addr, "REMOTE_ADDR");
SetEnv(request.env_http_host, "HTTP_HOST");
SetEnv(request.env_http_user_agent, "HTTP_USER_AGENT");
SetEnv(request.env_fcgi_role, "FCGI_ROLE");
SetEnv(request.env_content_type, "CONTENT_TYPE");
SetEnv(request.env_http_accept_encoding,"HTTP_ACCEPT_ENCODING");
}
void App::CheckRequestMethod()
{
request.method = Request::none;
if( ToSmall(request.env_request_method[0]) == 'g' )
request.method = Request::get;
else
if( ToSmall(request.env_request_method[0]) == 'p' )
request.method = Request::post;
else
if( ToSmall(request.env_request_method[0]) == 'h' )
request.method = Request::head;
}
void App::CheckFCGIRole()
{
// default we assume 'responder'
request.role = Request::responder;
if( ToSmall(request.env_fcgi_role[0]) == 'a' )
request.role = Request::authorizer;
}
void App::LogAccess()
{
log.PutDate(log1);
log << request.env_remote_addr << ' '
<< request.env_request_method << ' '
<< request.env_http_host
<< request.env_request_uri << ' '
<< request.env_http_user_agent << logend;
}
void App::ReadGetPostVars()
{
// get parameters we have always
get_parser.Parse(request.env_request_uri, request.get_tab);
if( request.method == Request::post )
{
if( IsSubStringNoCase("multipart/form-data", request.env_content_type) )
{
log << log3 << "Request: post content type: multipart/form-data" << logend;
post_multi_parser.Parse(request.in, request.post_tab, request.post_file_tab);
}
else
{
post_parser.Parse(request.in, request.post_tab);
}
}
}
void App::CheckIE()
{
char * msie = strstr(request.env_http_user_agent, "MSIE");
if( msie )
request.browser_msie = true;
else
request.browser_msie = false;
}
void App::CheckKonqueror()
{
char * kon = strstr(request.env_http_user_agent, "Konqueror");
if( kon )
request.browser_konqueror = true;
else
request.browser_konqueror = false;
}
void App::PrepareSessionCookie()
{
if( !request.session || request.session->id==0 )
return;
if( !request.session->puser || !request.session->remember_me )
{
request.SetCookie(config.http_session_id_name.c_str(), request.session->id);
}
else
{
time_t t = time(0) + config.session_remember_max_idle;
tm * expires = localtime(&t);
if( !expires )
{
// oops, something wrong
request.SetCookie(config.http_session_id_name.c_str(), request.session->id);
return;
}
request.SetCookie(config.http_session_id_name.c_str(), request.session->id, expires);
}
}
void App::SendHeaders(bool compressing, Header header)
{
PrepareSessionCookie();
if( request.send_as_attachment )
FCGX_PutS("Content-Disposition: attachment\r\n", request.out);
if( !request.redirect_to.empty() )
{
FCGX_PutS("Status: 301 Moved Permanently\r\n", request.out);
FCGX_FPrintF(request.out, "Location: %s\r\n", request.redirect_to.c_str());
log << log2 << "Redirect to: " << request.redirect_to << logend;
}
else
if( !request.x_sendfile.empty() )
{
FCGX_FPrintF(request.out, "%s: %s\r\n", config.http_header_send_file.c_str(),
request.x_sendfile.c_str());
FCGX_PutS("Status: 200 OK\r\n", request.out);
log << log2 << "Sending file: " << request.x_sendfile << logend;
}
else
{
switch( header )
{
case h_404:
FCGX_PutS("Status: 404 Not Found\r\n", request.out);
FCGX_PutS("Content-Type: text/html\r\n", request.out);
log << log2 << "Request: response: 404 Not Found" << logend;
break;
case h_403:
FCGX_PutS("Status: 403 Forbidden\r\n", request.out);
FCGX_PutS("Content-Type: text/html\r\n", request.out);
log << log2 << "Request: response: 403 Forbidden" << logend;
break;
default:
FCGX_PutS("Status: 200 OK\r\n", request.out);
if( request.role != Request::authorizer )
FCGX_PutS("Content-Type: text/html\r\n", request.out);
}
}
if( compressing )
FCGX_PutS("Content-Encoding: deflate\r\n", request.out);
FCGX_PutS(request.headers.str().c_str(), request.out);
FCGX_PutS("\r\n", request.out);
}
void App::SetHtmlFilterConf()
{
html_filter.TrimWhite(config.html_filter_trim_white);
html_filter.BreakLines(config.html_filter_break_lines);
html_filter.InsertTabs(config.html_filter_tabs);
if( config.html_filter_orphans )
html_filter.CheckOrphans(config.html_filter_orphans_lang, config.html_filter_orphans_mode);
}
// !! kopiowanie tych stringow bedzie zmienione
// gdy bedziemy korzystac w przyszlosci z wlasnego stringstream
void App::FilterCompressSend(bool compressing, const std::string & source_ref)
{
const std::string * source = &source_ref;
bool raw = request.is_item && request.item.content_type == Item::ct_raw && request.status == WINIX_ERR_OK &&
request.function && (request.function->fun.url == "cat" || request.function->fun.url == "run");
if( config.html_filter && !raw )
{
SetHtmlFilterConf();
html_filter.Filter(*source, clean_html);
AddDebugInfo(clean_html);
source = &clean_html;
}
else
{
html_with_debug = *source;
AddDebugInfo(html_with_debug);
source = &html_with_debug;
}
if( compressing )
compress.CompressAndPut(source->c_str(), source->length(), request.out);
else
FCGX_PutS(source->c_str(), request.out);
}
bool App::IsCompressionAllowed(const std::string & source)
{
return( config.compression &&
request.role == Request::responder &&
request.redirect_to.empty() &&
request.x_sendfile.empty() &&
!request.browser_msie &&
!request.browser_konqueror &&
accept_encoding_parser.AcceptDeflate() &&
source.size() >= (size_t)config.compression_page_min_size );
}
bool App::CanSendContent(Header header)
{
if( !request.redirect_to.empty() || !request.x_sendfile.empty() )
// if there is a redirect or a file to send then we do not send a content
return false;
if( header == h_200 && request.role == Request::authorizer && request.is_item && request.item.auth != Item::auth_none )
// if there is an item and the item has 'file' storage we do not send a content
return false;
/*
we don't have to check the HEAD method
the server (lighttpd) doesn't send the body of its own
*/
if( request.method == Request::head )
return false;
return true;
}
void App::AddDebugInfo(std::string & out)
{
if( config.debug_info )
{
const std::string & d = request.debug.str();
if( !d.empty() )
{
out += "\n<!--\n";
out += d;
out += "\n-->\n";
}
}
}
void App::SendAnswer()
{
const std::string & source = request.page.str();
Header header = h_200;
bool compressing = IsCompressionAllowed(source);
Error status = request.status;
if( status == WINIX_ERR_NO_ITEM || status == WINIX_ERR_NO_FUNCTION || status == WINIX_ERR_UNKNOWN_PARAM )
header = h_404;
if( status == WINIX_ERR_PERMISSION_DENIED || status == WINIX_ERR_CANT_CHANGE_USER || status == WINIX_ERR_CANT_CHANGE_GROUP )
header = h_403;
SendHeaders(compressing, header);
if( CanSendContent(header) )
{
// filtering (html), compressing (deflate) and sending back to the web browser
FilterCompressSend(compressing, source);
}
}