add config options: db_startup_connection_max_attempts - default 0 (infinite) db_startup_connection_attempt_delay - delay in seconds between attempts (default 5) BREAKING CHANGE: WINIX_PLUGIN_INIT plugin message requires to set result status, you have to set the result status to true (env.res) if your plugin was initialized correctly, otherwise winix will not start
1527 lines
33 KiB
C++
1527 lines
33 KiB
C++
/*
|
|
* 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 "system.h"
|
|
#include "misc.h"
|
|
#include "error.h"
|
|
#include "templates/templates.h"
|
|
#include "functions/functions.h"
|
|
|
|
|
|
|
|
namespace Winix
|
|
{
|
|
|
|
|
|
|
|
|
|
void System::SetCur(Cur * pcur)
|
|
{
|
|
cur = pcur;
|
|
}
|
|
|
|
|
|
//void System::SetConfig(Config * pconfig)
|
|
//{
|
|
// config = pconfig;
|
|
//}
|
|
|
|
|
|
void System::SetDb(Db * pdb)
|
|
{
|
|
db = pdb;
|
|
}
|
|
|
|
|
|
//void System::SetSynchro(Synchro * psynchro)
|
|
//{
|
|
// synchro = psynchro;
|
|
//}
|
|
|
|
|
|
void System::SetFunctions(Functions * pfunctions)
|
|
{
|
|
functions = pfunctions;
|
|
}
|
|
|
|
void System::SetSessionManager(SessionManager * sm)
|
|
{
|
|
session_manager = sm;
|
|
}
|
|
|
|
|
|
void System::set_dependency(WinixModelDeprecated * winix_model)
|
|
{
|
|
WinixModelDeprecated::set_dependency(winix_model);
|
|
|
|
dirs.set_dependency(this);
|
|
mounts.set_dependency(this);
|
|
//users.set_dependency(this);
|
|
users.set_connector(model_connector);
|
|
groups.set_dependency(this);
|
|
rebus.set_dependency(this);
|
|
load_avg.set_dependency(this);
|
|
notify.set_dependency(this);
|
|
image.set_dependency(this);
|
|
crypt.set_dependency(this);
|
|
thread_manager.set_dependency(this);
|
|
job.set_dependency(winix_model);
|
|
time_zones.set_dependency(winix_model);
|
|
}
|
|
|
|
|
|
void System::ReadTimeZones()
|
|
{
|
|
if( config->etc_dir.empty() )
|
|
{
|
|
log << log1 << "System: I cannot read time zones, set etc_dir directory in the config" << logend;
|
|
return;
|
|
}
|
|
|
|
if( config->time_zones_file.empty() )
|
|
{
|
|
log << log1 << "System:: I cannot read time zones, set time_zones_file in the config" << logend;
|
|
return;
|
|
}
|
|
|
|
name_temp = config->etc_dir;
|
|
name_temp += '/';
|
|
name_temp += config->time_zones_file;
|
|
|
|
time_zones.SetTimeZoneMaxId(config->time_zone_max_id);
|
|
time_zones.ReadTimeZones(name_temp);
|
|
}
|
|
|
|
|
|
bool System::Init()
|
|
{
|
|
//thread_manager.SetSynchro(synchro);
|
|
thread_manager.Init();
|
|
|
|
dirs.SetDb(db);
|
|
dirs.SetCur(cur); // only one method is using cur, can be passed as a parameter to the method
|
|
dirs.SetNotify(¬ify);
|
|
dirs.ReadDirs();
|
|
|
|
mounts.SkipStaticDirs(config->dont_use_static_dirs);
|
|
mounts.SetDirs(&dirs);
|
|
mounts.SetDb(db);
|
|
mounts.SetCur(cur); // only one method is using cur, can be passed as a parameter to the method
|
|
mounts.CreateMounts();
|
|
mounts.ReadMounts();
|
|
|
|
// users.SetCur(cur);
|
|
// users.SetSessionManager(session_manager);
|
|
users.set_connector(model_connector);
|
|
users.ReadUsers(db);
|
|
|
|
groups.ReadGroups(db); // !! chwilowe przekazanie argumentu, db bedzie zmienione
|
|
|
|
rebus.SetCur(cur);
|
|
rebus.Init();
|
|
|
|
|
|
notify.SetCur(cur);
|
|
//notify.SetConfig(config);
|
|
notify.SetUsers(&users);
|
|
notify.SetDirs(&dirs);
|
|
notify.SetThreadManager(&thread_manager);
|
|
|
|
if( !notify.Init() )
|
|
return false;
|
|
|
|
image.SetDb(db);
|
|
image.SetConfig(config);
|
|
image.SetSystem(this);
|
|
|
|
if( !thread_manager.Add(&image, L"image") )
|
|
return false;
|
|
|
|
// SetSynchro will be called by ThreadManager itself
|
|
// job.ReadFromFile();
|
|
if( !thread_manager.Add(&job, L"job") )
|
|
return false;
|
|
|
|
ReadTimeZones();
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* try_to_use_ssl is to be meant: config->use_ssl, config->use_ssl_static, config->use_ssl_common
|
|
*/
|
|
bool System::IsSSLRequired(bool try_to_use_ssl)
|
|
{
|
|
bool ssl = false;
|
|
|
|
if( try_to_use_ssl )
|
|
{
|
|
if( !config->use_ssl_only_for_logged_users ||
|
|
cur->session->puser ||
|
|
(cur->request->function && cur->request->function->need_ssl) )
|
|
{
|
|
ssl = true;
|
|
}
|
|
}
|
|
|
|
return ssl;
|
|
}
|
|
|
|
|
|
bool System::IsSSLRequired()
|
|
{
|
|
return IsSSLRequired(config->use_ssl);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* try_to_use_ssl is to be meant: config->use_ssl, config->use_ssl_static, config->use_ssl_common
|
|
*/
|
|
void System::PutUrlProto(bool can_use_ssl, std::wstring & str, bool clear_str)
|
|
{
|
|
if( clear_str )
|
|
str.clear();
|
|
|
|
if( IsSSLRequired() )
|
|
str += config->url_ssl_proto;
|
|
else
|
|
str += config->url_proto;
|
|
}
|
|
|
|
|
|
/*
|
|
* try_to_use_ssl is to be meant: config->use_ssl, config->use_ssl_static, config->use_ssl_common
|
|
*/
|
|
void System::PutUrlProto(bool can_use_ssl, pt::TextStream & str, bool clear_stream)
|
|
{
|
|
if( clear_stream )
|
|
str.clear();
|
|
|
|
if( IsSSLRequired(can_use_ssl) )
|
|
{
|
|
str << config->url_ssl_proto;
|
|
}
|
|
else
|
|
{
|
|
str << config->url_proto;
|
|
}
|
|
}
|
|
|
|
|
|
void System::PutUrlProto(std::wstring & str, bool clear_str)
|
|
{
|
|
return PutUrlProto(config->use_ssl, str, clear_str);
|
|
}
|
|
|
|
|
|
void System::PutUrlProto(pt::TextStream & str, bool clear_stream)
|
|
{
|
|
return PutUrlProto(config->use_ssl, str, clear_stream);
|
|
}
|
|
|
|
|
|
void System::CreateItemLink(long parent_id, const std::wstring & url, const std::wstring & subdomain,
|
|
std::wstring & link, bool clear_str)
|
|
{
|
|
PutUrlProto(link, clear_str);
|
|
|
|
if( !subdomain.empty() )
|
|
{
|
|
link += subdomain;
|
|
link += '.';
|
|
}
|
|
|
|
link += config->base_url;
|
|
dirs.MakePath(parent_id, link, false); // !! IMPROVE ME may some kind of error checks here?
|
|
link += url;
|
|
}
|
|
|
|
|
|
void System::CreateItemLink(const Item & item, std::wstring & link, bool clear_str)
|
|
{
|
|
CreateItemLink(item.parent_id, item.url, cur->request->subdomain, link, clear_str);
|
|
}
|
|
|
|
|
|
|
|
// !! IMPROVE ME
|
|
// !! mozna zrobic jakas obsluge kiedy nie mozemy sie redirectnac, np gdy wystapil blad
|
|
// !! moze zwracac jakas wartosc?
|
|
/*
|
|
postfix will not be UrlEncoded
|
|
*/
|
|
void System::RedirectTo(const Item & item, const wchar_t * postfix, bool use_reqtype)
|
|
{
|
|
PutUrlProto(cur->request->redirect_to);
|
|
|
|
if( !cur->request->subdomain.empty() )
|
|
{
|
|
cur->request->redirect_to += cur->request->subdomain;
|
|
cur->request->redirect_to += '.';
|
|
}
|
|
|
|
cur->request->redirect_to += config->base_url;
|
|
|
|
if( item.type == Item::dir )
|
|
{
|
|
// item_id is pointing to a directory
|
|
dirs.MakePath(item.id, cur->request->redirect_to, false);
|
|
}
|
|
else
|
|
{
|
|
// item_id is pointing to a file or a symlink
|
|
if( dirs.MakePath(item.parent_id, cur->request->redirect_to, false) )
|
|
cur->request->redirect_to += item.url;
|
|
}
|
|
|
|
if( postfix )
|
|
cur->request->redirect_to += postfix;
|
|
|
|
if( use_reqtype && cur->request->IsParam(L"reqtype") )
|
|
{
|
|
cur->request->redirect_to += L"/-/reqtype:";
|
|
cur->request->redirect_to += cur->request->ParamValue(L"reqtype");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
postfix will not be UrlEncoded
|
|
*/
|
|
void System::RedirectTo(long item_id, const wchar_t * postfix, bool use_reqtype)
|
|
{
|
|
PutUrlProto(cur->request->redirect_to);
|
|
|
|
if( !cur->request->subdomain.empty() )
|
|
{
|
|
cur->request->redirect_to += cur->request->subdomain;
|
|
cur->request->redirect_to += '.';
|
|
}
|
|
|
|
cur->request->redirect_to += config->base_url;
|
|
Item * pdir = dirs.GetDir(item_id);
|
|
|
|
if( pdir )
|
|
{
|
|
// item_id is pointing to a directory
|
|
dirs.MakePath(pdir->id, cur->request->redirect_to, false);
|
|
}
|
|
else
|
|
{
|
|
// item_id is pointing to a file
|
|
// DbItemQuery iq;
|
|
|
|
// iq.SetAllSel(false);
|
|
// iq.WhereId(item_id);
|
|
// iq.sel_parent_id = true;
|
|
// iq.sel_url = true;
|
|
|
|
morm::Finder<Item> finder(model_connector);
|
|
|
|
item_temp = finder.
|
|
select().
|
|
where().
|
|
eq(L"id", item_id).
|
|
get();
|
|
|
|
//if( db->GetItem(item_temp, iq) == WINIX_ERR_OK )
|
|
if( item_temp.found() )
|
|
{
|
|
if( dirs.MakePath(item_temp.parent_id, cur->request->redirect_to, false) )
|
|
cur->request->redirect_to += item_temp.url;
|
|
}
|
|
else
|
|
{
|
|
log << log1 << "System: can't redirect: no such item: id: " << item_id << logend;
|
|
}
|
|
}
|
|
|
|
if( postfix )
|
|
cur->request->redirect_to += postfix;
|
|
|
|
if( use_reqtype && cur->request->IsParam(L"reqtype") )
|
|
{
|
|
cur->request->redirect_to += L"/-/reqtype:";
|
|
cur->request->redirect_to += cur->request->ParamValue(L"reqtype");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
url will not be UrlEncoded
|
|
*/
|
|
void System::RedirectTo(const wchar_t * url, bool use_reqtype)
|
|
{
|
|
PutUrlProto(cur->request->redirect_to);
|
|
|
|
if( !cur->request->subdomain.empty() )
|
|
{
|
|
cur->request->redirect_to += cur->request->subdomain;
|
|
cur->request->redirect_to += '.';
|
|
}
|
|
|
|
cur->request->redirect_to += config->base_url;
|
|
|
|
if( url[0] == '/' )
|
|
{
|
|
// absolute path
|
|
cur->request->redirect_to += url;
|
|
}
|
|
else
|
|
{
|
|
// relative path
|
|
if( !cur->request->dir_tab.empty() )
|
|
{
|
|
if( dirs.MakePath(cur->request->dir_tab.back()->id, cur->request->redirect_to, false) )
|
|
cur->request->redirect_to += url;
|
|
}
|
|
else
|
|
{
|
|
cur->request->redirect_to += '/';
|
|
cur->request->redirect_to += url;
|
|
}
|
|
}
|
|
|
|
if( use_reqtype && cur->request->IsParam(L"reqtype") )
|
|
{
|
|
cur->request->redirect_to += L"/-/reqtype:";
|
|
cur->request->redirect_to += cur->request->ParamValue(L"reqtype");
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
url will not be UrlEncoded
|
|
*/
|
|
void System::RedirectTo(const std::wstring & url, bool use_reqtype)
|
|
{
|
|
RedirectTo(url.c_str(), use_reqtype);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
params will be UrlEncoded
|
|
*/
|
|
void System::AddParams(const ParamTab & param_tab, std::wstring & str, bool clear_str)
|
|
{
|
|
if( clear_str )
|
|
str.clear();
|
|
|
|
for(size_t i=0 ; i<param_tab.size() ; ++i)
|
|
{
|
|
str += '/';
|
|
UrlEncode(param_tab[i].name, str, false);
|
|
|
|
if( !param_tab[i].value.empty() )
|
|
{
|
|
str += ':';
|
|
UrlEncode(param_tab[i].value, str, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
url will not be UrlEncoded
|
|
params will be UrlEncoded
|
|
*/
|
|
void System::RedirectWithFunctionAndParamsTo(const wchar_t * url)
|
|
{
|
|
RedirectTo(url);
|
|
|
|
if( !cur->request->function )
|
|
return;
|
|
|
|
cur->request->redirect_to += '/';
|
|
cur->request->redirect_to += cur->request->function->fun.url;
|
|
AddParams(cur->request->param_tab, cur->request->redirect_to, false);
|
|
}
|
|
|
|
|
|
/*
|
|
url will not be UrlEncoded
|
|
params will be UrlEncoded
|
|
*/
|
|
void System::RedirectWithFunctionAndParamsTo(const std::wstring & url)
|
|
{
|
|
RedirectWithFunctionAndParamsTo(url.c_str());
|
|
}
|
|
|
|
|
|
void System::RedirectToLastDir(const wchar_t * postfix, bool use_reqtype)
|
|
{
|
|
if( !cur->request->dir_tab.empty() )
|
|
RedirectTo( *cur->request->dir_tab.back(), postfix, use_reqtype);
|
|
}
|
|
|
|
|
|
void System::RedirectToLastItem(const wchar_t * postfix, bool use_reqtype)
|
|
{
|
|
if( cur->request->last_item )
|
|
RedirectTo( *cur->request->last_item, postfix, use_reqtype );
|
|
}
|
|
|
|
|
|
void System::RedirectToLastFunction(const wchar_t * postfix, bool use_reqtype)
|
|
{
|
|
RedirectToLastDir(0, false);
|
|
TrimLast(cur->request->redirect_to, '/');
|
|
|
|
if( cur->request->is_item )
|
|
{
|
|
cur->request->redirect_to += '/';
|
|
cur->request->redirect_to += cur->request->item.url;
|
|
}
|
|
|
|
if( cur->request->function )
|
|
{
|
|
cur->request->redirect_to += '/';
|
|
cur->request->redirect_to += cur->request->function->fun.url;
|
|
}
|
|
|
|
if( postfix )
|
|
{
|
|
cur->request->redirect_to += '/';
|
|
cur->request->redirect_to += postfix;
|
|
}
|
|
|
|
if( use_reqtype && cur->request->IsParam(L"reqtype") )
|
|
{
|
|
if( !cur->request->function && !postfix )
|
|
cur->request->redirect_to += L"/-";
|
|
|
|
cur->request->redirect_to += L"/reqtype:";
|
|
cur->request->redirect_to += cur->request->ParamValue(L"reqtype");
|
|
}
|
|
|
|
}
|
|
|
|
|
|
bool System::CanChangeUser(const Item & item, long new_user_id)
|
|
{
|
|
if( !cur->session )
|
|
// session must be set
|
|
return false;
|
|
|
|
if( cur->session->puser && cur->session->puser->is_super_user )
|
|
// super user is allowed everything
|
|
return true;
|
|
|
|
if( item.item_content.user_id == -1 || new_user_id == -1 || item.item_content.user_id != new_user_id )
|
|
// only super user can change the owner of an item
|
|
return false;
|
|
|
|
// item.user_id is equal new_user_id -- we return true
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool System::CanChangeGroup(const Item & item, long new_group_id)
|
|
{
|
|
if( !cur->session )
|
|
// session must be set
|
|
return false;
|
|
|
|
if( cur->session->puser && cur->session->puser->is_super_user )
|
|
// super user is allowed everything
|
|
return true;
|
|
|
|
if( item.item_content.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( !cur->session->puser || cur->session->puser->id == -1 )
|
|
return false;
|
|
|
|
if( item.item_content.user_id == -1 || cur->session->puser->id != item.item_content.user_id )
|
|
return false;
|
|
|
|
if( new_group_id == -1 )
|
|
return true;
|
|
|
|
if( !cur->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 System::CanChangePrivileges(const Item & item, int new_priv)
|
|
{
|
|
if( !cur->session )
|
|
// session must be set
|
|
return false;
|
|
|
|
if( cur->session->puser && cur->session->puser->is_super_user )
|
|
// super user is allowed everything
|
|
return true;
|
|
|
|
if( item.item_content.privileges != new_priv )
|
|
{
|
|
// the owner of an item is allowed to change the privileges
|
|
|
|
if( !cur->session->puser || cur->session->puser->id == -1 )
|
|
return false;
|
|
|
|
if( item.item_content.user_id == -1 || cur->session->puser->id != item.item_content.user_id )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// DEPRACATED
|
|
bool System::HasReadAccess(const Item & item)
|
|
{
|
|
return item.item_content.has_read_access();
|
|
}
|
|
|
|
|
|
// DEPRACATED
|
|
bool System::HasWriteAccess(const Item & item)
|
|
{
|
|
return item.item_content.has_write_access();
|
|
}
|
|
|
|
|
|
// DEPRACATED
|
|
bool System::HasReadWriteAccess(const Item & item)
|
|
{
|
|
return item.item_content.has_read_write_access();
|
|
}
|
|
|
|
|
|
// DEPRACATED
|
|
bool System::HasReadExecAccess(const Item & item)
|
|
{
|
|
return item.item_content.has_read_exec_access();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool System::HasReadExecAccessToPath(long dir_id)
|
|
{
|
|
while( true )
|
|
{
|
|
Item * pdir = dirs.GetDir(dir_id);
|
|
|
|
if( !pdir )
|
|
return false;
|
|
|
|
if( !HasReadExecAccess(*pdir) )
|
|
return false;
|
|
|
|
dir_id = pdir->parent_id;
|
|
|
|
if( dir_id == -1 )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
bool System::HasReadExecAccessToPath(const std::vector<Item*> & dir_tab)
|
|
{
|
|
for(size_t i=0 ; i < dir_tab.size() ; ++i)
|
|
{
|
|
if( !HasReadExecAccess(*dir_tab[i]) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool System::DirsHaveReadExecPerm()
|
|
{
|
|
return HasReadExecAccessToPath(cur->request->dir_tab);
|
|
}
|
|
|
|
|
|
// if we don't have access we only remove the item from the table
|
|
// !! moze zamienic nazwe na CheckReadAccessToItems ?
|
|
void System::CheckAccessToItems(std::vector<Item> & item_tab)
|
|
{
|
|
size_t i = 0;
|
|
|
|
while( i < item_tab.size() )
|
|
{
|
|
if( !HasReadAccess(item_tab[i]) )
|
|
{
|
|
item_tab.erase(item_tab.begin() + i);
|
|
}
|
|
else
|
|
{
|
|
i += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void System::CheckWriteAccessToItems(std::vector<Item> & item_tab)
|
|
{
|
|
size_t i = 0;
|
|
|
|
while( i < item_tab.size() )
|
|
{
|
|
if( !HasWriteAccess(item_tab[i]) )
|
|
{
|
|
item_tab.erase(item_tab.begin() + i);
|
|
}
|
|
else
|
|
{
|
|
i += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int System::NewPrivileges(int creation_mask)
|
|
{
|
|
if( cur && cur->session && cur->session->puser )
|
|
{
|
|
int umask = cur->session->puser->env.to_int(L"umask", config->umask);
|
|
return (~umask) & creation_mask;
|
|
}
|
|
else
|
|
{
|
|
return (~config->umask) & creation_mask;
|
|
}
|
|
}
|
|
|
|
/*
|
|
from man sticky:
|
|
A directory whose `sticky bit' is set becomes an append-only directory,
|
|
or, more accurately, a directory in which the deletion of files is
|
|
restricted. A file in a sticky directory may only be removed or renamed
|
|
by a user if the user has write permission for the directory and the user
|
|
is the owner of the file, the owner of the directory, or the super-user.
|
|
This feature is usefully applied to directories such as /tmp which must
|
|
be publicly writable but should deny users the license to arbitrarily
|
|
delete or rename each others' files.
|
|
*/
|
|
bool System::CanRemoveRenameChild(const Item & dir, long child_item_user_id)
|
|
{
|
|
if( dir.type != Item::dir )
|
|
return false;
|
|
|
|
if( !HasWriteAccess(dir) )
|
|
return false;
|
|
|
|
if( (dir.item_content.privileges & 010000) == 0 )
|
|
// there is no a sticky bit set to this directory
|
|
return true;
|
|
|
|
if( cur->session->puser )
|
|
{
|
|
if( cur->session->puser->is_super_user )
|
|
return true;
|
|
|
|
if( dir.item_content.user_id != -1 && cur->session->puser->id != -1 && child_item_user_id != -1 )
|
|
{
|
|
if( cur->session->puser->id == child_item_user_id ||
|
|
cur->session->puser->id == dir.item_content.user_id )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
int System::NewFilePrivileges()
|
|
{
|
|
return NewPrivileges(06666);
|
|
}
|
|
|
|
|
|
int System::NewDirPrivileges()
|
|
{
|
|
return NewPrivileges(07777);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool System::CanUseHtml(long user_id)
|
|
{
|
|
return IsSuperUser(user_id) || IsMemberOfGroup(user_id, L"allow_html");
|
|
}
|
|
|
|
|
|
bool System::CanUseBBCode(long user_id)
|
|
{
|
|
// logged users can use bbcode
|
|
return (user_id != -1);
|
|
}
|
|
|
|
|
|
// !! IMPROVE ME change to a better name
|
|
bool System::CanUseOther(long user_id)
|
|
{
|
|
return IsSuperUser(user_id) || IsMemberOfGroup(user_id, L"allow_other");
|
|
}
|
|
|
|
|
|
|
|
bool System::IsSuperUser(long user_id)
|
|
{
|
|
User * puser = users.GetUser(user_id);
|
|
|
|
if( !puser )
|
|
return false;
|
|
|
|
return puser->is_super_user;
|
|
}
|
|
|
|
|
|
bool System::IsMemberOfGroup(long user_id, const wchar_t * group_name)
|
|
{
|
|
User * puser = users.GetUser(user_id);
|
|
|
|
if( !puser )
|
|
return false;
|
|
|
|
long group = groups.GetGroupId(group_name);
|
|
|
|
if( group == -1 )
|
|
// there is no such a group
|
|
return false;
|
|
|
|
return puser->IsMemberOf(group);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// the path depends on parent_id
|
|
bool System::CreateNewFileSimpleFs(Item & item)
|
|
{
|
|
bool res = dirs.MakePath(item.parent_id, item.item_content.file_path);
|
|
|
|
if( res )
|
|
{
|
|
if( !item.item_content.file_path.empty() && item.item_content.file_path[0] == '/' )
|
|
item.item_content.file_path.erase(0, 1);
|
|
}
|
|
else
|
|
{
|
|
log << log1 << "System: CreateNewFileSimpleFs: can't create a path to item.id: " << item.id
|
|
<< ", item.parent_id: " << item.parent_id << logend;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
|
|
// the path depends on id
|
|
bool System::CreateNewFileHashFs(Item & item)
|
|
{
|
|
wchar_t buffer[50];
|
|
wchar_t * hash = buffer;
|
|
size_t buffer_len = sizeof(buffer)/sizeof(wchar_t);
|
|
|
|
// get 'id' as hexadecimal
|
|
buffer[0] = '0';
|
|
swprintf(buffer+1, buffer_len, L"%lx", (unsigned long)item.id);
|
|
|
|
item.item_content.file_path.clear();
|
|
|
|
// make sure that the length is even
|
|
if( (wcslen(hash) & 1) != 0 )
|
|
hash = buffer + 1; // the first character was zero
|
|
|
|
for(size_t i=0 ; hash[i] != 0 ; i+=2)
|
|
{
|
|
item.item_content.file_path += hash[i];
|
|
item.item_content.file_path += hash[i+1];
|
|
|
|
if( hash[i+2] != 0 )
|
|
item.item_content.file_path += '/';
|
|
}
|
|
|
|
// one character more to make sure the path is unique
|
|
// (we can have a directory without the character)
|
|
item.item_content.file_path += '_';
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// creating item.file_path and item.file_fs
|
|
// you should set file_type yourself
|
|
// this method uses: item.id, item.url, item.parent_id (for selecting a mountpoint)
|
|
bool System::CreateNewFile(Item & item)
|
|
{
|
|
bool res;
|
|
|
|
if( item.type != Item::file )
|
|
{
|
|
log << log1 << "System: CreateNewFile: the item should be a file" << logend;
|
|
return false;
|
|
}
|
|
|
|
Mount * pmount = mounts.CalcMount(item.parent_id);
|
|
|
|
if( !pmount || pmount->fs != mounts.MountFsHashfs() )
|
|
{
|
|
res = CreateNewFileSimpleFs(item);
|
|
item.item_content.file_fs = mounts.MountFsSimplefs();
|
|
}
|
|
else
|
|
{
|
|
res = CreateNewFileHashFs(item);
|
|
item.item_content.file_fs = mounts.MountFsHashfs();
|
|
}
|
|
|
|
if( res )
|
|
item.item_content.file_path += item.url;
|
|
else
|
|
item.item_content.file_path.clear();
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
// making a global file path (in the unix file system)
|
|
// you should call CreateNewFile before
|
|
bool System::MakeFilePath(const Item & item, std::wstring & path, bool thumb, bool create_dir, int chmod, int group)
|
|
{
|
|
path.clear();
|
|
|
|
if( config->upload_dir.empty() )
|
|
{
|
|
log << log1 << "System: MakePath: upload_dir is not set in the config file" << logend;
|
|
return false;
|
|
}
|
|
|
|
if( item.item_content.file_path.empty() || item.item_content.file_type == WINIX_ITEM_FILETYPE_NONE )
|
|
{
|
|
log << log1 << "System: MakePath: this item has not a static file" << logend;
|
|
return false;
|
|
}
|
|
|
|
path = config->upload_dir;
|
|
|
|
if( item.item_content.file_fs == mounts.MountFsHashfs() )
|
|
path += L"/hashfs";
|
|
else
|
|
path += L"/simplefs";
|
|
|
|
|
|
if( thumb )
|
|
path += L"/thumb";
|
|
else
|
|
path += L"/normal";
|
|
|
|
|
|
if( create_dir && !CreateDirs(path, item.item_content.file_path, chmod, group, true) )
|
|
return false;
|
|
|
|
path += '/';
|
|
path += item.item_content.file_path;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool System::MakeRelativeFilePath(const Item & item, const std::wstring & path_prefix, std::wstring & path, bool thumb)
|
|
{
|
|
path.clear();
|
|
|
|
if( item.item_content.file_path.empty() || item.item_content.file_type == WINIX_ITEM_FILETYPE_NONE )
|
|
{
|
|
log << log1 << "System: MakePath: this item has not a static file" << logend;
|
|
return false;
|
|
}
|
|
|
|
// we allow the prefix to be empty
|
|
if( !path_prefix.empty() )
|
|
{
|
|
if( path_prefix[0] != '/' )
|
|
path += '/';
|
|
|
|
path += path_prefix;
|
|
TrimLast(path, '/');
|
|
}
|
|
|
|
if( item.item_content.file_fs == mounts.MountFsHashfs() )
|
|
path += L"/hashfs";
|
|
else
|
|
path += L"/simplefs";
|
|
|
|
if( thumb )
|
|
path += L"/thumb";
|
|
else
|
|
path += L"/normal";
|
|
|
|
path += '/';
|
|
path += item.item_content.file_path;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// item can be a directory, file or a symlink
|
|
// if item is a directory then the path will be with a slash at the end
|
|
bool System::MakePath(const Item & item, std::wstring & path, bool clear_path)
|
|
{
|
|
bool res;
|
|
|
|
if( clear_path )
|
|
path.clear();
|
|
|
|
if( item.type == Item::dir )
|
|
{
|
|
res = dirs.MakePath(item.id, path);
|
|
}
|
|
else
|
|
{
|
|
res = dirs.MakePath(item.parent_id, path);
|
|
|
|
if( res )
|
|
path += item.url;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool System::AddFile(Item & item, int notify_code, bool call_plugins)
|
|
{
|
|
if( item.type != Item::file )
|
|
return WINIX_ERR_FILE_EXPECTED;
|
|
|
|
|
|
//Error status = db->AddItem(item);
|
|
item.set_connector(model_connector);
|
|
bool status = item.insert();
|
|
|
|
if( status )
|
|
{
|
|
log << log2 << "System: added a new file, url: " << item.url << ", id: " << item.id
|
|
<< ", parent_id: " << item.parent_id << logend;
|
|
|
|
if( notify_code )
|
|
notify.ItemChanged(notify_code, item);
|
|
|
|
if( call_plugins )
|
|
plugin->Call(WINIX_FILE_ADDED, &item);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool System::EditFile(Item & item, bool with_url, int notify_code, bool call_plugins)
|
|
{
|
|
if( item.type != Item::file )
|
|
return WINIX_ERR_FILE_EXPECTED;
|
|
|
|
if( cur->session && cur->session->puser )
|
|
cur->request->item.item_content.modification_user_id = cur->session->puser->id;
|
|
else
|
|
cur->request->item.item_content.modification_user_id = -1;
|
|
|
|
item.item_content.SetDateModifyToNow();
|
|
|
|
ItemModelData item_model_data;
|
|
item_model_data.prepare_unique_url = with_url;
|
|
|
|
bool status = item.update(item_model_data);
|
|
//Error status = db->EditItemById(item, with_url);
|
|
|
|
if( status )
|
|
{
|
|
TemplatesFunctions::pattern_cacher.UpdatePattern(item);
|
|
|
|
log << log2 << "System: modified an item" << logend;
|
|
|
|
if( notify_code )
|
|
notify.ItemChanged(notify_code, item);
|
|
|
|
if( call_plugins )
|
|
plugin->Call(WINIX_FILE_CHANGED, &item);
|
|
}
|
|
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
time_t System::ToLocal(time_t utc_time)
|
|
{
|
|
size_t tz_id;
|
|
|
|
if( cur->session && cur->session->puser )
|
|
tz_id = cur->session->puser->time_zone_id;
|
|
else
|
|
tz_id = config->time_zone_default_id;
|
|
|
|
TimeZone * tz = time_zones.GetZone(tz_id);
|
|
|
|
if( tz )
|
|
return tz->ToLocal(utc_time);
|
|
|
|
return utc_time;
|
|
}
|
|
|
|
|
|
|
|
pt::Date System::ToLocal(const pt::Date & utc_date)
|
|
{
|
|
size_t tz_id;
|
|
|
|
if( cur->session && cur->session->puser )
|
|
tz_id = cur->session->puser->time_zone_id;
|
|
else
|
|
tz_id = config->time_zone_default_id;
|
|
|
|
TimeZone * tz = time_zones.GetZone(tz_id);
|
|
|
|
if( tz )
|
|
return tz->ToLocal(utc_date);
|
|
|
|
return utc_date;
|
|
}
|
|
|
|
|
|
|
|
|
|
time_t System::ToUTC(time_t local_time)
|
|
{
|
|
size_t tz_id;
|
|
|
|
if( cur->session && cur->session->puser )
|
|
tz_id = cur->session->puser->time_zone_id;
|
|
else
|
|
tz_id = config->time_zone_default_id;
|
|
|
|
TimeZone * tz = time_zones.GetZone(tz_id);
|
|
|
|
if( tz )
|
|
return tz->ToUTC(local_time);
|
|
|
|
return local_time;
|
|
}
|
|
|
|
|
|
|
|
pt::Date System::ToUTC(const pt::Date & local_date)
|
|
{
|
|
size_t tz_id;
|
|
|
|
if( cur->session && cur->session->puser )
|
|
tz_id = cur->session->puser->time_zone_id;
|
|
else
|
|
tz_id = config->time_zone_default_id;
|
|
|
|
TimeZone * tz = time_zones.GetZone(tz_id);
|
|
|
|
if( tz )
|
|
return tz->ToUTC(local_date);
|
|
|
|
return local_date;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
return codes:
|
|
ok:
|
|
0 - the link_to is a path to a directory (out_item skipped, out_dir_tab will not be empty)
|
|
1 - the link_to is a path to a file or a symlink (out_item is used, out_dir_tab will not be empty)
|
|
|
|
error:
|
|
2 - incorrect link_to
|
|
3 - there is not a root dir
|
|
4 - current_dir_tab was empty
|
|
|
|
current_dir_tab can be the same container as out_dir_tab
|
|
link_to can be a relative path (without the first slash)
|
|
*/
|
|
int System::FollowLink(const std::vector<Item*> & current_dir_tab, const std::wstring & link_to,
|
|
std::vector<Item*> & out_dir_tab, Item & out_item)
|
|
{
|
|
link_to_temp = link_to; // copy the link because it can be from out_item (and will be cleared)
|
|
out_item.Clear();
|
|
|
|
int res = dirs.FollowLink(current_dir_tab, link_to_temp, out_dir_tab, name_temp);
|
|
|
|
if( res == 1 )
|
|
{
|
|
morm::Finder<Item> finder(model_connector);
|
|
|
|
bool status = finder.
|
|
select().
|
|
where().
|
|
eq(L"parent_id", out_dir_tab.back()->id).
|
|
eq(L"url", name_temp).
|
|
get(out_item);
|
|
|
|
//if( db->GetItem(out_dir_tab.back()->id, name_temp, out_item) == WINIX_ERR_OK )
|
|
if( status )
|
|
return 1;
|
|
else
|
|
return 2;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool System::FollowAllLinksDirFound(std::vector<Item*> & out_dir_tab,
|
|
bool follow_dir_default, bool stop_on_link_redirect, bool check_access)
|
|
{
|
|
log << log3 << "System: link to a directory: ";
|
|
dirs.LogDir(out_dir_tab);
|
|
log << logend;
|
|
|
|
if( check_access && !HasReadExecAccessToPath(out_dir_tab) )
|
|
{
|
|
log << log1 << "System: no access to the directory structure" << logend;
|
|
return false;
|
|
}
|
|
|
|
if( !out_dir_tab.back()->item_content.link_to.empty() )
|
|
{
|
|
if( follow_dir_default )
|
|
{
|
|
if( !(stop_on_link_redirect && out_dir_tab.back()->item_content.link_redirect==1) )
|
|
link_to_temp = out_dir_tab.back()->item_content.link_to;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
bool System::FollowAllLinksFileOrSymlinkFound(std::vector<Item*> & out_dir_tab, Item & out_item,
|
|
bool stop_on_link_redirect, bool check_access)
|
|
{
|
|
if( out_item.type == Item::symlink )
|
|
log << log3 << "System: link to a symlink: ";
|
|
else
|
|
log << log3 << "System: link to a file: ";
|
|
|
|
dirs.LogDir(out_dir_tab);
|
|
log << out_item.url << logend;
|
|
|
|
if( check_access && !HasReadExecAccessToPath(out_dir_tab) )
|
|
{
|
|
log << log1 << "System: no access to the directory structure" << logend;
|
|
return false;
|
|
}
|
|
|
|
if( check_access && !HasReadAccess(out_item) )
|
|
{
|
|
log << log1 << "System: no read access to the file or symlink" << logend;
|
|
return false;
|
|
}
|
|
|
|
if( out_item.type == Item::symlink )
|
|
{
|
|
if( out_item.item_content.link_to.empty() )
|
|
{
|
|
log << log1 << "System: symlink empty" << logend;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if( !(stop_on_link_redirect && out_item.item_content.link_redirect==1) )
|
|
link_to_temp = out_item.item_content.link_to;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
return codes:
|
|
ok:
|
|
0 - the link_to is a path to a directory (out_item skipped, out_dir_tab will not be empty)
|
|
1 - the link_to is a path to a file (out_item is used, out_dir_tab will not be empty)
|
|
(link_to can be a path to a symlink if stop_on_link_redirect is true)
|
|
|
|
error:
|
|
2 - incorrect link_to
|
|
3 - there is not a root dir
|
|
4 - current_dir_tab was empty
|
|
5 - limit of symlinks exceeded
|
|
6 - permission denied to a file/symlink or a directory (or a symlink is empty)
|
|
|
|
current_dir_tab can be the same container as out_dir_tab
|
|
link_to can be a relative path (without the first slash)
|
|
|
|
if follow_dir_default is true then directories with 'default' flags are followed as well
|
|
|
|
if stop_on_link_redirect is true then the method stops on a symbolic link (or a directory
|
|
with 'default' flag set) where the redirection should be done, you should check then whether
|
|
the out_item.back()->link_to is not empty (if the result was 0 ) or
|
|
whether out_item.type is symlink (if the result was 1)
|
|
to make the redirection
|
|
*/
|
|
int System::FollowAllLinks(const std::vector<Item*> & current_dir_tab, const std::wstring & link_to,
|
|
std::vector<Item*> & out_dir_tab, Item & out_item,
|
|
bool follow_dir_default, bool stop_on_link_redirect, bool check_access)
|
|
{
|
|
int res;
|
|
size_t level = 0;
|
|
link_to_temp = link_to;
|
|
|
|
if( current_dir_tab.empty() )
|
|
return 4;
|
|
|
|
do
|
|
{
|
|
if( ++level > config->symlinks_follow_max )
|
|
{
|
|
log << log1 << "System: the number of maximum symlinks exceeded ("
|
|
<< config->symlinks_follow_max << ")" << logend;
|
|
|
|
res = 5;
|
|
}
|
|
else
|
|
{
|
|
// !! CHECK ME
|
|
// FollowLink is using link_to_temp temporary variable too
|
|
res = FollowLink(current_dir_tab, link_to_temp, out_dir_tab, out_item);
|
|
link_to_temp.clear();
|
|
|
|
if( res == 0 )
|
|
{
|
|
out_item.Clear();
|
|
res = FollowAllLinksDirFound(out_dir_tab, follow_dir_default, stop_on_link_redirect, check_access) ? 0 : 6;
|
|
}
|
|
else
|
|
if( res == 1 )
|
|
{
|
|
res = FollowAllLinksFileOrSymlinkFound(out_dir_tab, out_item, stop_on_link_redirect, check_access) ? 1 : 6;
|
|
}
|
|
else
|
|
{
|
|
log << log2 << "System: incorrect link: " << link_to << logend;
|
|
}
|
|
}
|
|
}
|
|
while( !link_to_temp.empty() );
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
// the same as FollowAllLinks but starts from the root directory
|
|
// link_to must begin with a slash (or can be empty then the root directory is returned)
|
|
int System::FollowAllLinks(const std::wstring & link_to,
|
|
std::vector<Item*> & out_dir_tab, Item & out_item,
|
|
bool follow_dir_default, bool stop_on_link_redirect, bool check_access)
|
|
{
|
|
if( !link_to.empty() && link_to[0] != '/' )
|
|
return 2;
|
|
|
|
if( root_follow_dir_tab.size() != 1 )
|
|
root_follow_dir_tab.resize(1);
|
|
|
|
Item * root_dir = dirs.GetRootDir();
|
|
|
|
if( !root_dir )
|
|
return 3;
|
|
|
|
root_follow_dir_tab[0] = root_dir;
|
|
|
|
return FollowAllLinks(root_follow_dir_tab, link_to,
|
|
out_dir_tab, out_item, follow_dir_default, stop_on_link_redirect, check_access);
|
|
}
|
|
|
|
|
|
|
|
// the same as FollowAllLinks but operates on cur->request->dir_tab and cur->request->item
|
|
// and returns bool
|
|
// the method is making a redirection if needed
|
|
bool System::FollowAllLinks(const std::wstring & link_to,
|
|
bool follow_dir_default, bool stop_on_link_redirect, bool check_access)
|
|
{
|
|
int res = FollowAllLinks(cur->request->dir_tab, link_to, temp_follow_dir_tab, temp_follow_item,
|
|
follow_dir_default, stop_on_link_redirect, check_access);
|
|
|
|
bool ok = (res == 0 || res == 1);
|
|
|
|
if( ok )
|
|
{
|
|
cur->request->dir_tab = temp_follow_dir_tab;
|
|
|
|
if( res == 0 )
|
|
{
|
|
cur->request->is_item = false;
|
|
cur->request->item.Clear();
|
|
cur->request->last_item = cur->request->dir_tab.back();
|
|
|
|
if( !cur->request->dir_tab.back()->item_content.link_to.empty() )
|
|
RedirectTo(cur->request->dir_tab.back()->item_content.link_to);
|
|
|
|
log << log3 << "System: current directory changed" << logend;
|
|
}
|
|
else
|
|
{
|
|
cur->request->is_item = true;
|
|
cur->request->item = temp_follow_item;
|
|
cur->request->last_item = &cur->request->item;
|
|
|
|
if( cur->request->item.type == Item::symlink )
|
|
RedirectTo(cur->request->item.item_content.link_to); // cur->request->item.item_content.link_to is not empty
|
|
|
|
log << log3 << "System: current directory changed and the new file loaded" << logend;
|
|
}
|
|
|
|
mounts.CalcCurMount();
|
|
}
|
|
else
|
|
{
|
|
if( res == 5 || res == 6 )
|
|
cur->request->status = WINIX_ERR_PERMISSION_DENIED;
|
|
else
|
|
cur->request->status = WINIX_ERR_NO_ITEM;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
adding a new file to winix
|
|
file_path - a name of a static file (from common directory)
|
|
url - url of a file which will be inserted (in /var directory)
|
|
|
|
current limitation:
|
|
warning: the url is not prepared by PrepareUrl() (PrepareUrl is from functions)
|
|
*/
|
|
bool System::AddCommonFileToVar(const wchar_t * file_path, const wchar_t * url, const wchar_t * mime_type, bool overwrite_existing)
|
|
{
|
|
if( config->common_dir.empty() )
|
|
{
|
|
log << log1 << "System: can't open a file from common directory, common_dir not set in the config" << logend;
|
|
return false;
|
|
}
|
|
|
|
file_name = config->common_dir;
|
|
file_name += '/';
|
|
file_name += file_path;
|
|
|
|
if( !GetUTF8File(file_name, file_content) )
|
|
{
|
|
log << log1 << "System: can't open a file: " << file_name << logend;
|
|
return false;
|
|
}
|
|
|
|
Item * var = dirs.CreateVarDir();
|
|
|
|
if( !var )
|
|
{
|
|
log << log1 << "System: can't create /var directory" << logend;
|
|
return false;
|
|
}
|
|
|
|
morm::Finder<Item> finder(model_connector);
|
|
|
|
file_content_item = finder.select().where().eq(L"parent_id", var->id).eq(L"url", url).get();
|
|
|
|
if( file_content_item.found() )
|
|
{
|
|
if( !overwrite_existing )
|
|
return true;
|
|
|
|
file_content_item.remove();
|
|
}
|
|
|
|
file_content_item.Clear();
|
|
file_content_item.parent_id = var->id;
|
|
file_content_item.item_content.user_id = var->item_content.user_id;
|
|
file_content_item.item_content.group_id = var->item_content.group_id;
|
|
file_content_item.item_content.privileges = 07555; // !! IMPROVE ME: may it should be added as a parameter to this function?
|
|
file_content_item.subject = url;
|
|
file_content_item.url = url;
|
|
file_content_item.type = Item::file;
|
|
file_content_item.html_template = config->templates_index_raw;
|
|
file_content_item.item_content.content_raw = file_content;
|
|
file_content_item.item_content.content_raw_type = ItemContent::ct_other;
|
|
|
|
if( mime_type )
|
|
file_content_item.item_content.file_mime_type = mime_type;
|
|
|
|
return AddFile(file_content_item, false);
|
|
}
|
|
|
|
|
|
|
|
} // namespace Winix
|
|
|