winix/core/request.cpp

747 lines
13 KiB
C++
Executable File

/*
* This file is a part of CMSLU -- Content Management System like Unix
* and is not publicly distributed
*
* Copyright (c) 2008-2009, Tomasz Sowa
* All rights reserved.
*
*/
#include <ctime>
#include "request.h"
#include "getparser.h"
#include "postparser.h"
#include "cookieparser.h"
#include "log.h"
#include "data.h"
#include "plugin.h"
#include "misc.h"
Request::Request() : char_empty(0)
{
id = 0;
Clear();
}
void Request::Init()
{
compress.Init();
}
void Request::Clear()
{
// warning: don't clear: in, out, err, env
// id is never 0
if( ++id == 0 )
++id;
get_table.clear();
post_table.clear();
cookie_table.clear();
method = none;
headers.str("");
page.str("");
debug.str("");
notify.str("");
env_request_method = &char_empty;
env_request_uri = &char_empty;
env_http_cookie = &char_empty;
env_remote_addr = &char_empty;
env_http_host = &char_empty;
env_http_user_agent = &char_empty;
env_http_accept_encoding = &char_empty;
session = 0;
item_table.clear();
item.Clear();
dir_table.clear();
is_item = false;
pfunction = 0;
param_table.clear();
status = Error::ok;
is_thread = false;
thread.Clear();
thread_tab.clear();
notify_code = 0;
browser_msie = false;
redirect_to.clear();
plugin.Call(CMSLU_REQUEST_CLEAR);
}
// value can be null
void Request::SetCookie(const char * name, const char * value, tm * expires)
{
headers << "Set-Cookie: " << name << "=";
if( value && value[0]!=0 )
headers << value;
else
headers << "\"\"";
if( expires )
headers << "; expires=" << DateToStrCookie(expires) << " GMT";
headers << "; path=/\r\n";
}
void Request::SetCookie(const char * name, long value, tm * expires)
{
headers << "Set-Cookie: " << name << "=" << value;
if( expires )
headers << "; expires=" << DateToStrCookie(expires) << " GMT";
headers << "; path=/\r\n";
}
bool Request::IsPostVar(const char * var)
{
PostTable::iterator p;
p = post_table.find(var);
if( p == post_table.end() )
return false;
return true;
}
std::string * Request::PostVar(const char * var)
{
PostTable::iterator p = post_table.find(var);
if( p == post_table.end() )
return 0;
return &(p->second);
}
bool Request::PostVar(const char * var, std::string & result)
{
PostTable::iterator p = post_table.find(var);
if( p == post_table.end() )
{
result.clear();
return false;
}
result = p->second;
return true;
}
//
void Request::PrintGetTable()
{
debug << "get_table: " << get_table.size() << "\n";
for(GetTable::iterator i = get_table.begin() ; i != get_table.end() ; ++i)
debug << " \"" << *i << "\"\n";
debug << std::endl;
}
void Request::PrintEnv()
{
char ** e;
debug << "environment variables:\n";
for( e = env ; *e ; ++e )
debug << ' ' << *e << "\n";
debug << std::endl;
}
void Request::PrintIn()
{
char buf[100];
int buf_len = sizeof(buf) / sizeof(char);
int len;
debug << "fcgi input:\n";
do
{
len = FCGX_GetStr(buf, buf_len - 1, in);
if( len != 0 )
{
buf[len] = 0;
debug << buf;
}
}
while( len == buf_len - 1 );
debug << std::endl;
}
const char * Request::SetEnvVar(const char * var)
{
const char * v = FCGX_GetParam(var, env);
if( v )
return v;
// char_empty contains '\0'
return &char_empty;
}
void Request::ReadEnvVariables()
{
// we store that values because FCGX_GetParam has O(n) complexity
// with this variables (env_*) we have O(1)
env_request_method = SetEnvVar("REQUEST_METHOD");
env_request_uri = SetEnvVar("REQUEST_URI");
env_http_cookie = SetEnvVar("HTTP_COOKIE");
env_remote_addr = SetEnvVar("REMOTE_ADDR");
env_http_host = SetEnvVar("HTTP_HOST");
env_http_user_agent = SetEnvVar("HTTP_USER_AGENT");
env_http_accept_encoding = SetEnvVar("HTTP_ACCEPT_ENCODING");
}
void Request::CheckIE()
{
char * msie = strstr(env_http_user_agent, "MSIE");
if( msie )
browser_msie = true;
else
browser_msie = false;
}
void Request::CheckKonqueror()
{
char * kon = strstr(env_http_user_agent, "Konqueror");
if( kon )
browser_konqueror = true;
else
browser_konqueror = false;
}
void Request::CheckMethod()
{
method = none;
if( env_request_method[0] == 'G' )
method = get;
else
if( env_request_method[0] == 'P' )
method = post;
}
bool Request::AllPostVarEmpty()
{
PostTable::iterator i;
for(i=post_table.begin() ; i!=post_table.end() ; ++i)
if( !i->second.empty() )
return false;
return true;
}
void Request::ReadParameters()
{
GetParser get_parser(env_request_uri, get_table);
get_parser.Parse();
if( method == post )
{
PostParser post_parser(in, post_table);
post_parser.Parse();
}
CookieParser cookie_parser(env_http_cookie, cookie_table);
cookie_parser.Parse();
accept_encoding_parser.Parse(env_http_accept_encoding);
}
void Request::StandardLog()
{
log.PutDate(log1);
log << env_remote_addr << ' ' << env_request_method << ' ' << env_request_uri << ' ' << env_http_user_agent << logend;
}
// reading everything
void Request::Read()
{
ReadEnvVariables();
StandardLog();
CheckMethod();
ReadParameters();
CheckIE();
CheckKonqueror();
}
void Request::SendSessionCookie()
{
if( !session || session->id==0 )
return;
if( !session->puser || !session->remember_me )
{
SetCookie(data.http_session_id_name.c_str(), session->id);
return;
}
time_t t = time(0) + data.session_remember_max_iddle;
tm * expires = localtime(&t);
if( !expires )
{
// oops, something wrong
SetCookie(data.http_session_id_name.c_str(), session->id);
return;
}
SetCookie(data.http_session_id_name.c_str(), session->id, expires);
}
void Request::SendHeaders(bool compressing)
{
if( !redirect_to.empty() )
{
FCGX_PutS("Status: 301 Moved Permanently\r\n", out);
FCGX_FPrintF(out, "Location: %s\r\n", redirect_to.c_str());
log << log2 << "Redirect to: " << redirect_to << logend;
}
else
{
FCGX_PutS("Status: 200 OK\r\n", out);
FCGX_PutS("Content-Type: Text/Html\r\n", out);
}
if( compressing )
FCGX_PutS("Content-Encoding: deflate\r\n", out);
FCGX_PutS(headers.str().c_str(), out);
FCGX_PutS("\r\n", out);
}
void Request::AddDebugInfo()
{
const std::string & d = debug.str();
if( !d.empty() )
{
page << "\n<!--\n";
page << d;
page << "\n-->\n";
}
}
void Request::SendPage(bool compressing)
{
const std::string & source_ref = page.str();
const std::string * source = &source_ref;
if( data.html_filter )
{
html_filter.TrimWhite(true);
html_filter.BreakLongLines(true);
html_filter.InsertTabs(2);
html_filter.Filter(*source, clean_html);
source = &clean_html;
}
if( compressing )
compress.CompressAndPut(source->c_str(), source->length(), out);
else
FCGX_PutS(source->c_str(), out);
}
void Request::SendAll()
{
bool compressing = data.compression && !browser_msie && !browser_konqueror && accept_encoding_parser.AcceptDeflate();
SendSessionCookie();
SendHeaders(compressing);
if( !redirect_to.empty() )
// if there is a redirect we do not send a content
return;
// adding debug info if exists
AddDebugInfo();
// sending content
SendPage(compressing);
}
bool Request::IsParam(const char * s)
{
std::vector<std::string*>::iterator i;
for(i=param_table.begin() ; i!=param_table.end() ; ++i)
{
if( **i == s )
return true;
}
return false;
}
bool Request::CanChangeUser(const Item & item, long new_user_id)
{
if( !session )
// session must be set
return false;
if( session->puser && session->puser->super_user )
// super user is allowed everything
return true;
if( item.user_id != new_user_id )
// only super user can change the owner of an item
return false;
return true;
}
bool Request::CanChangeGroup(const Item & item, long new_group_id)
{
if( !session )
// session must be set
return false;
if( session->puser && session->puser->super_user )
// super user is allowed everything
return true;
if( item.group_id != new_group_id )
{
// user is allowed to change the group only if he is an owner of the item
// he can change only into a group in which he is a member of, or into a 'no_group'
if( !session->puser )
return false;
if( session->puser->id != item.user_id )
return false;
if( new_group_id == -1 )
return true;
if( !session->puser->IsMemberOf(new_group_id) )
return false;
// is logged, is the owner of the item, is the member of the new group
}
return true;
}
bool Request::CanChangePrivileges(const Item & item, int new_priv)
{
if( !session )
// session must be set
return false;
if( session->puser && session->puser->super_user )
// super user is allowed everything
return true;
if( item.privileges != new_priv )
{
// the owner of an item is allowed to change the privileges
if( !session->puser )
return false;
if( session->puser->id != item.user_id )
return false;
}
return true;
}
bool Request::HasAccess(const Item & item, int mask)
{
if( !session )
// session must be set
return false;
if( session->puser && session->puser->super_user )
// super user is allowed everything
return true;
if( session->puser && session->puser->id == item.user_id )
{
// the owner
return ((item.privileges >> 6) & mask) == mask;
}
if( session->puser && session->puser->IsMemberOf(item.group_id) )
{
// group
return ((item.privileges >> 3) & mask) == mask;
}
// others
return (item.privileges & mask) == mask;
}
bool Request::HasReadAccess(const Item & item)
{
return HasAccess(item, 4);
}
bool Request::HasWriteAccess(const Item & item)
{
return HasAccess(item, 2);
}
bool Request::HasReadWriteAccess(const Item & item)
{
return HasAccess(item, 6); // r+w
}
bool Request::HasReadExecAccess(const Item & item)
{
if( session && session->puser && session->puser->super_user )
{
// there must be at least one 'x' (for the root)
return (item.privileges & 0111) != 0;
}
return HasAccess(item, 5); // r+x
}
// returning true if we can create a thread in the current directory
bool Request::CanCreateThread(bool check_root)
{
if( request.dir_table.empty() )
return false;
if( request.is_item )
return false;
if( !HasWriteAccess(*request.dir_table.back()) )
return false;
if( data.mounts.CurrentMountType() != Mount::thread )
return false;
if( !check_root && session && session->puser && session->puser->super_user )
// super can create thread regardless of the restrictcreatethread option
return true;
// !! w przyszlosci mozna odczytywac wiecej parametrow od restrictcreatethread
// tymczasowo wykorzystujemy tylko pierwszy
int level;
if( data.mounts.CurrentMountIsParam(Mount::restrictcreatethread, &level) )
{
if( level == -1 )
return false;
// we can only allow on a specific level
if( int(request.dir_table.size()) != level )
return false;
}
return true;
}
bool Request::CanRemove(const Item & item)
{
if( item.parent_id == -1 )
{
// rm for the root dir
// only the superuser can do it
if( !request.session->puser || !request.session->puser->super_user )
return false;
}
else
{
Item * last_but_one_dir = data.dirs.GetDir(item.parent_id);
if( !last_but_one_dir )
// ops, there is no a parent dir
return false;
if( !request.HasWriteAccess(*last_but_one_dir) )
return false;
}
if( data.mounts.CurrentMountIsParam(Mount::only_root_can_remove) )
// this can be deleted only be a root
if( !request.session->puser || !request.session->puser->super_user )
return false;
return true;
}
bool Request::CanUseEmacs(const Item & item, bool check_root)
{
if( !check_root && request.session->puser && request.session->puser->super_user )
// super user can use emacs everywhere
return true;
if( !request.HasWriteAccess(item) )
return false;
int level;
if( data.mounts.CurrentMountIsParam(Mount::can_use_emacs_on, &level) )
if( level != int(request.dir_table.size()) )
return false;
return true;
}
bool Request::CanUseMkdir(const Item & item, bool check_root)
{
// you can create a directory only in a directory
if( item.type != Item::dir )
return false;
if( !check_root && request.session->puser && request.session->puser->super_user )
// super user can use mkdir everywhere
return true;
if( !request.HasWriteAccess(item) )
return false;
int level;
if( data.mounts.CurrentMountIsParam(Mount::can_use_mkdir_on, &level) )
if( level != int(request.dir_table.size()) )
return false;
return true;
}
bool Request::CanUseHtml(long user_id)
{
User * puser = data.users.GetUser(user_id);
if( !puser )
return false;
if( puser->super_user )
// super user can use html
return true;
long group = data.groups.GetGroupId("allow_html");
if( group == -1 )
// there is no such a group
return false;
if( puser->IsMemberOf(group) )
return true;
return false;
}
bool Request::CanUseBBCode(long user_id)
{
User * puser = data.users.GetUser(user_id);
if( !puser )
return false;
if( puser->super_user )
// super user can use bbcode
return true;
long group = data.groups.GetGroupId("allow_bbcode");
if( group == -1 )
// there is no such a group
return false;
if( puser->IsMemberOf(group) )
return true;
return false;
}