winix/winixd/functions/rm.cpp

579 lines
14 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) 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();
}
/*
* !! IMPROVE ME what about a Content-Type header for javascript?
*/
void Rm::CreateJSON(bool status)
{
using TemplatesFunctions::R;
pt::WTextStream buf;
JSONescape(cur->request->item.url, buf);
auto & out = cur->request->out_main_stream;
out << R("{\"files\": [{\"") << R(buf) << R("\": ");
if( status )
out << R("true");
else
out << R("false");
out << R("}]}");
cur->request->page_generated = true;
cur->request->out_main_stream_use_html_filter = false;
}
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