winix/winixd/functions/rm.cpp

567 lines
13 KiB
C++
Raw Normal View History

/*
* 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) 2008-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 <cstdio>
#include "rm.h"
#include "core/misc.h"
#include "templates/templates.h"
namespace Winix
{
namespace Fun
{
Rm::Rm()
{
fun.url = L"rm";
follow_symlinks = false;
}
bool Rm::HasAccessToDir(const Item & dir, bool only_content)
{
if( dir.parent_id == -1 )
{
// this is a root directory
// we can only remove the content of the root directory
// sticky bit for a specified child will be checked later
if( !only_content || !system->HasWriteAccess(dir) )
return false;
}
else
if( only_content )
{
// sticky bit for a specified child will be checked later
if( !system->HasWriteAccess(dir) )
return false;
}
else
{
Item * last_but_one_dir = system->dirs.GetDir(dir.parent_id);
if( !last_but_one_dir )
// ops, there is no a parent dir
return false;
if( !system->CanRemoveRenameChild(*last_but_one_dir, dir.item_content.user_id) )
return false;
}
return true;
}
/*
here we are making a little test -- this method returns false if
we are sure that the permissions is not allowed (consequently the html form
will not be displayed)
the correct checking for permissions is done later in MakePost() method
parameter 'c' to rm function means removing only the content of a directory
this parameter can be sent either by a get or a post variable
*/
bool Rm::HasAccess()
{
bool res = false;
if( cur->request->is_item )
{
res = system->CanRemoveRenameChild(*cur->request->dir_tab.back(), cur->request->item.item_content.user_id);
}
else
{
if( cur->request->IsParam(L"r") )
{
bool only_content = (cur->request->IsParam(L"c") || cur->request->IsPostVar(L"c"));
res = HasAccessToDir(*cur->request->dir_tab.back(), only_content);
}
else
{
log << log3 << "Rm: directories can be removed only with 'r' parameter" << logend;
slog << logerror << T("rm_use_r_option") << logend;
}
}
if( !res && cur->request->IsParam(L"jquery_upload") )
CreateJSON(res);
return res;
}
void Rm::Prepare()
{
// selecting files and symlinks (without directories)
// content_dir_iq.SetAll(false, false);
// content_dir_iq.sel_parent_id = true;
// content_dir_iq.sel_type = true;
// content_dir_iq.sel_url = true;
// content_dir_iq.sel_file = true;
// content_dir_iq.sel_user_id = true;
// content_dir_iq.sel_group_id = true;
// content_dir_iq.sel_privileges = true;
// content_dir_iq.sel_meta = true;
// content_dir_iq.WhereType(Item::dir, false);
}
void Rm::RemoveStaticFile(const std::wstring & path)
{
if( Winix::RemoveFile(path) )
{
log << log2 << "Rm: static file removed: " << path << logend;
}
else
{
log << log1 << "Rm: I can't remove a static file: " << path << logend;
slog << logerror << T("rm_cannot_remove_static_file") << ": " << path << logend;
}
}
void Rm::RemoveStaticFile(const Item & item)
{
if( system->MakeFilePath(item, path, false) )
{
RemoveStaticFile(path);
if( item.item_content.file_has_thumb && system->MakeFilePath(item, path, true) )
RemoveStaticFile(path);
}
else
{
log << log1 << "Rm: I cannot create a path to a static file, url: "
<< item.url << ", id: " << item.id << logend;
slog << logerror << T("rm_cannot_create_static_path") << ": " << item.url << logend;
}
}
/*
!! IMPROVE ME:
plugin.Call(WINIX_FILE_REMOVED, &item) is getting its parameter as non-const
in the future we can add a pointer cp1, cp2 (in plugins) but pointing to a const object
and this bool Rm::RemoveFile(Item & item) can became bool Rm::RemoveFile(const Item & item)
*/
bool Rm::RemoveFile(Item & item)
{
plugin->Call(WINIX_FILE_PREPARE_TO_REMOVE, &item);
//if( db->DelItem(item) == WINIX_ERR_OK )
if( item.remove() )
{
if( item.type == Item::file )
log << log2 << "Rm: deleted file: ";
else
log << log2 << "Rm: deleted symlink: ";
log << item.url << logend;
TemplatesFunctions::pattern_cacher.DeletePattern(item);
if( item.item_content.file_type != WINIX_ITEM_FILETYPE_NONE )
RemoveStaticFile(item);
plugin->Call(WINIX_FILE_REMOVED, &item);
return true;
}
return false;
}
/*
we do not modify item
it is non-const because we use plugin.Call() later with a pointer to this structure
(and we want to avoid casting)
*/
bool Rm::RemoveFileOrSymlink(Item & item, bool check_access)
{
if( item.type == Item::dir )
return false;
if( check_access )
{
Item * dir = system->dirs.GetDir(item.parent_id);
// if there is not 'dir' directory then we can simply remove 'item'
if( dir )
{
if( !system->CanRemoveRenameChild(*dir, item.item_content.user_id) )
{
log << log1 << "Rm: permission denied to remove: " << item.url << ", id: " << item.id << logend;
slog << logerror << T("rm_permission_denied_to") << ": " << item.url << logend;
return false;
}
}
}
return RemoveFile(item);
}
bool Rm::RemoveFileOrSymlink(long item_id, bool check_access)
{
bool result = false;
// selecting files, symlinks and directories
// rm_by_id_iq.SetAll(false, false);
// rm_by_id_iq.sel_parent_id = true;
// rm_by_id_iq.sel_type = true;
// rm_by_id_iq.sel_url = true;
// rm_by_id_iq.sel_file = true;
// rm_by_id_iq.sel_user_id = true;
// rm_by_id_iq.sel_group_id = true;
// rm_by_id_iq.sel_privileges = true;
// rm_by_id_iq.sel_meta = true;
// rm_by_id_iq.WhereId(item_id);
morm::Finder<Item> finder(model_connector);
rm_by_id_item = finder.
select().
where().
eq(L"id", item_id).
get();
//if( db->GetItem(rm_by_id_item, rm_by_id_iq) == WINIX_ERR_OK )
if( rm_by_id_item.found() )
{
if( rm_by_id_item.type == Item::file || rm_by_id_item.type == Item::symlink )
{
result = RemoveFileOrSymlink(rm_by_id_item, check_access);
}
else
{
log << log2 << "Rm: I cannot remove file or symlink, item_id: " << item_id
<< " is a directory" << logend;
}
}
return result;
}
// for other uses (plugins etc)
bool Rm::RemoveItemById(long item_id, bool check_access)
{
bool result = false;
// selecting files, symlinks and directories
// rm_by_id_iq.SetAll(false, false);
// rm_by_id_iq.sel_parent_id = true;
// rm_by_id_iq.sel_type = true;
// rm_by_id_iq.sel_url = true;
// rm_by_id_iq.sel_file = true;
// rm_by_id_iq.sel_user_id = true;
// rm_by_id_iq.sel_group_id = true;
// rm_by_id_iq.sel_privileges = true;
// rm_by_id_iq.sel_meta = true;
// rm_by_id_iq.WhereId(item_id);
morm::Finder<Item> finder(model_connector);
rm_by_id_item = finder.
select().
where().
eq(L"id", item_id).
get();
//if( db->GetItem(rm_by_id_item, rm_by_id_iq) == WINIX_ERR_OK )
if( rm_by_id_item.found() )
{
if( rm_by_id_item.type == Item::dir )
{
RemoveDirTree(rm_by_id_item, true, check_access);
result = true; // RemoveDirTree doesn't return a status
}
else
{
result = RemoveFileOrSymlink(rm_by_id_item, check_access);
}
}
return result;
}
bool Rm::RemoveItemByPath(const std::wstring & path, bool check_access)
{
bool result = false;
int res = system->FollowAllLinks(path, rm_path_dir_tab, rm_path_item);
if( res == 0 )
{
Item * dir = system->dirs.GetDir(rm_path_dir_tab.back()->id);
if( dir )
{
RemoveDirTree(*dir, true, check_access);
result = false;
}
}
else
if( res == 1 )
{
result = RemoveFileOrSymlink(rm_path_item, check_access);
}
return result;
}
bool Rm::RemoveDirFiles(long dir_id, bool check_access)
{
// content_dir_iq.WhereParentId(dir_id);
// db->GetItems(content_item_tab, content_dir_iq);
morm::Finder<Item> finder(model_connector);
finder.
select().
where().
eq(L"parent_id", dir_id).
get_vector(content_item_tab);
size_t removed = 0;
for(size_t i=0 ; i<content_item_tab.size() ; ++i)
if( RemoveFileOrSymlink(content_item_tab[i], check_access) )
removed += 1;
return removed == content_item_tab.size();
}
/*
parent_dir can be null (when current_dir is the root directory)
*/
void Rm::RemoveCurrentDir(Item * parent_dir, Item * current_dir, bool check_access)
{
if( check_access )
{
if( !parent_dir || !system->CanRemoveRenameChild(*parent_dir, current_dir->item_content.user_id) )
{
log << log1 << "Rm: permission denied to directory: " << current_dir->url << logend;
slog << logerror << T("rm_permission_denied_to") << ": " << current_dir->url << logend;
return;
}
}
plugin->Call(WINIX_DIR_PREPARE_TO_REMOVE, current_dir);
//if( db->DelDirById(current_dir->id) == WINIX_ERR_OK )
if( current_dir->remove() )
{
long dir_id = current_dir->id;
old_url = current_dir->url;
system->dirs.DelDir(dir_id);
// don't use current_dir pointer anymore
log << log2 << "Rm: directory removed: " << old_url << logend;
plugin->Call(WINIX_DIR_REMOVED, dir_id);
}
else
{
log << log1 << "Rm: I cannot remove a directory: " << current_dir->url << " (database error)" << logend;
}
}
/*
parent_dir can be null (when current_dir is pointing to the root directory)
current_dir is pointing to the directory which should be deleted
(contents of this directory is removed but the directory is deleted only if remove_this_dir is true)
*/
void Rm::RemoveDirTree(Item * parent_dir, Item * current_dir, bool remove_this_dir, bool check_access)
{
if( check_access && !system->HasReadExecAccess(*current_dir) )
{
log << log1 << "Rm: permission denied to directory: " << current_dir->url << logend;
slog << logerror << T("rm_permission_denied_to") << ": " << current_dir->url << logend;
return;
}
DirContainer::ParentIterator pnext, p = system->dirs.FindFirstChild(current_dir->id);
for( ; p != system->dirs.ParentEnd() ; p = pnext )
{
Item * child_dir = &(*p->second);
// this iterator p will be invalidated by the below RemoveDirTree() call
// (the next iterator we must calculate beforehand)
pnext = system->dirs.NextChild(p);
RemoveDirTree(current_dir, child_dir, true, check_access);
}
bool all_file_removed = RemoveDirFiles(current_dir->id, check_access);
if( remove_this_dir )
{
if( all_file_removed )
{
RemoveCurrentDir(parent_dir, current_dir, check_access);
// don't use current_dir pointer anymore
}
else
{
log << log1 << "Rm: " << current_dir->url << " directory not empty" << logend;
slog << logerror << current_dir->url << T("rm_directory_not_empty") << logend;
}
}
}
void Rm::RemoveDirTree(Item & dir, bool remove_this_dir, bool check_access)
{
Item * parent = system->dirs.GetDir(dir.parent_id);
RemoveDirTree(parent, &dir, remove_this_dir, check_access);
}
void Rm::RemoveDirContent()
{
if( !cur->request->IsParam(L"r") )
{
cur->request->status = WINIX_ERR_PERMISSION_DENIED;
log << log3 << "Rm: directory content can be removed only with 'r' parameter" << logend;
slog << logerror << T("rm_content_use_r_option") << logend;
return;
}
RemoveDirTree(*cur->request->dir_tab.back(), false, true);
}
void Rm::RemoveDir()
{
if( !cur->request->IsParam(L"r") )
{
cur->request->status = WINIX_ERR_PERMISSION_DENIED;
log << log3 << "Rm: a directory can be removed only with 'r' parameter" << logend;
slog << logerror << T("rm_use_r_option") << logend;
return;
}
if( cur->request->dir_tab.size() <= 1 )
{
cur->request->status = WINIX_ERR_PERMISSION_DENIED;
log << log1 << "Rm: the root directory cannot be removed" << logend;
slog << logerror << T("rm_cannot_remove_root_dir") << logend;
return;
}
RemoveDirTree(*cur->request->dir_tab.back(), true, true);
cur->request->dir_tab.erase(--cur->request->dir_tab.end());
}
void Rm::Clear()
{
content_item_tab.clear();
changed the way how the request's answer is created, now winix can return json, xml, csv from out_main_stream or from frames and json from models removed from Request: bool send_bin_stream bool return_json bool return_info_only pt::Space info bool page_generated bool out_main_stream_use_html_filter bool out_streams_use_html_filter added to Request: enum AnswerSource enum AnswerContainer AnswerSource answer_source AnswerContainer answer_container bool use_ezc_engine std::wstring frame bool send_all_frames bool use_html_filter added to Config: // the name of the url parameter for returning all frames, e.g. https://domain.tld/mydir/myfunction/allframes // default: allframes std::wstring request_all_frames_parameter; // the name of the root element when serializing request answer to xml // default: winix std::wstring xml_root; algorithm (the whole algorithm is described in core/request.h): at the beginning of a request winix sets answer_source to models answer_container to text use_ezc_engine to true next answer_container and use_ezc_engine can be changed in the following way: 1. winix will look for 'Accept' http header and depending on the header winix will set: (not implemented yet) Accept | answer_container | use_ezc_engine ------------------------------------|----------------- application/json | json | false application/xml | xml | false text/csv | csv | false 2. next answer_container is set depending on 'container' url parameter container | answer_container --------------------------------------------------------- not present | don't change the value text | text json | json xml | xml csv | csv use_ezc_engine is set depending on 'answer' url parameter: answer | use_ezc_engine --------------------------------- not present | don't change the value html | true data | false if 'answer' is html then we take into account two more parameters: frame: frame_name (empty default) - if set then winix returns this specific frame allframes: (if present then winix returns all frames)
2021-10-13 01:27:14 +02:00
files.clear();
}
/*
changed the way how the request's answer is created, now winix can return json, xml, csv from out_main_stream or from frames and json from models removed from Request: bool send_bin_stream bool return_json bool return_info_only pt::Space info bool page_generated bool out_main_stream_use_html_filter bool out_streams_use_html_filter added to Request: enum AnswerSource enum AnswerContainer AnswerSource answer_source AnswerContainer answer_container bool use_ezc_engine std::wstring frame bool send_all_frames bool use_html_filter added to Config: // the name of the url parameter for returning all frames, e.g. https://domain.tld/mydir/myfunction/allframes // default: allframes std::wstring request_all_frames_parameter; // the name of the root element when serializing request answer to xml // default: winix std::wstring xml_root; algorithm (the whole algorithm is described in core/request.h): at the beginning of a request winix sets answer_source to models answer_container to text use_ezc_engine to true next answer_container and use_ezc_engine can be changed in the following way: 1. winix will look for 'Accept' http header and depending on the header winix will set: (not implemented yet) Accept | answer_container | use_ezc_engine ------------------------------------|----------------- application/json | json | false application/xml | xml | false text/csv | csv | false 2. next answer_container is set depending on 'container' url parameter container | answer_container --------------------------------------------------------- not present | don't change the value text | text json | json xml | xml csv | csv use_ezc_engine is set depending on 'answer' url parameter: answer | use_ezc_engine --------------------------------- not present | don't change the value html | true data | false if 'answer' is html then we take into account two more parameters: frame: frame_name (empty default) - if set then winix returns this specific frame allframes: (if present then winix returns all frames)
2021-10-13 01:27:14 +02:00
*
*/
void Rm::CreateJSON(bool status)
{
changed the way how the request's answer is created, now winix can return json, xml, csv from out_main_stream or from frames and json from models removed from Request: bool send_bin_stream bool return_json bool return_info_only pt::Space info bool page_generated bool out_main_stream_use_html_filter bool out_streams_use_html_filter added to Request: enum AnswerSource enum AnswerContainer AnswerSource answer_source AnswerContainer answer_container bool use_ezc_engine std::wstring frame bool send_all_frames bool use_html_filter added to Config: // the name of the url parameter for returning all frames, e.g. https://domain.tld/mydir/myfunction/allframes // default: allframes std::wstring request_all_frames_parameter; // the name of the root element when serializing request answer to xml // default: winix std::wstring xml_root; algorithm (the whole algorithm is described in core/request.h): at the beginning of a request winix sets answer_source to models answer_container to text use_ezc_engine to true next answer_container and use_ezc_engine can be changed in the following way: 1. winix will look for 'Accept' http header and depending on the header winix will set: (not implemented yet) Accept | answer_container | use_ezc_engine ------------------------------------|----------------- application/json | json | false application/xml | xml | false text/csv | csv | false 2. next answer_container is set depending on 'container' url parameter container | answer_container --------------------------------------------------------- not present | don't change the value text | text json | json xml | xml csv | csv use_ezc_engine is set depending on 'answer' url parameter: answer | use_ezc_engine --------------------------------- not present | don't change the value html | true data | false if 'answer' is html then we take into account two more parameters: frame: frame_name (empty default) - if set then winix returns this specific frame allframes: (if present then winix returns all frames)
2021-10-13 01:27:14 +02:00
pt::Space & file = files.add_empty_space();
file.add(cur->request->item.url, status);
changed the way how the request's answer is created, now winix can return json, xml, csv from out_main_stream or from frames and json from models removed from Request: bool send_bin_stream bool return_json bool return_info_only pt::Space info bool page_generated bool out_main_stream_use_html_filter bool out_streams_use_html_filter added to Request: enum AnswerSource enum AnswerContainer AnswerSource answer_source AnswerContainer answer_container bool use_ezc_engine std::wstring frame bool send_all_frames bool use_html_filter added to Config: // the name of the url parameter for returning all frames, e.g. https://domain.tld/mydir/myfunction/allframes // default: allframes std::wstring request_all_frames_parameter; // the name of the root element when serializing request answer to xml // default: winix std::wstring xml_root; algorithm (the whole algorithm is described in core/request.h): at the beginning of a request winix sets answer_source to models answer_container to text use_ezc_engine to true next answer_container and use_ezc_engine can be changed in the following way: 1. winix will look for 'Accept' http header and depending on the header winix will set: (not implemented yet) Accept | answer_container | use_ezc_engine ------------------------------------|----------------- application/json | json | false application/xml | xml | false text/csv | csv | false 2. next answer_container is set depending on 'container' url parameter container | answer_container --------------------------------------------------------- not present | don't change the value text | text json | json xml | xml csv | csv use_ezc_engine is set depending on 'answer' url parameter: answer | use_ezc_engine --------------------------------- not present | don't change the value html | true data | false if 'answer' is html then we take into account two more parameters: frame: frame_name (empty default) - if set then winix returns this specific frame allframes: (if present then winix returns all frames)
2021-10-13 01:27:14 +02:00
cur->request->models.Add(L"files", files);
}
void Rm::MakePost()
{
Prepare();
if( cur->request->is_item )
{
RemoveFileOrSymlink(cur->request->item, true);
}
else
{
if( cur->request->IsParam(L"c") || cur->request->IsPostVar(L"c") )
RemoveDirContent();
else
RemoveDir();
}
Clear();
if( cur->request->IsParam(L"jquery_upload") )
CreateJSON(true);
else
if( cur->request->status == WINIX_ERR_OK )
system->RedirectToLastDir();
}
} // namespace
} // namespace Winix