551 lines
13 KiB
C++
551 lines
13 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-2018, 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.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.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.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.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.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.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);
|
|
|
|
if( db->GetItem(rm_by_id_item, rm_by_id_iq) == WINIX_ERR_OK )
|
|
{
|
|
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);
|
|
|
|
if( db->GetItem(rm_by_id_item, rm_by_id_iq) == WINIX_ERR_OK )
|
|
{
|
|
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);
|
|
|
|
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->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 )
|
|
{
|
|
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(buf, cur->request->item.url);
|
|
|
|
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
|
|
|