1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-21 23:32:35 +00:00

fix: restore portable file path check (#6853)

* chore: change to snake_case naming

* fix: restore portable file path check

* fix: macosx build
This commit is contained in:
Yat Ho 2024-05-25 23:08:53 +08:00 committed by GitHub
parent adc405e5be
commit 9748f42c5a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 97 additions and 89 deletions

View file

@ -134,7 +134,7 @@ tr_metainfo_builder::tr_metainfo_builder(std::string_view single_file_or_parent_
: top_{ single_file_or_parent_directory }
{
files_ = findFiles(tr_sys_path_dirname(top_), tr_sys_path_basename(top_));
block_info_ = tr_block_info{ files_.totalSize(), default_piece_size(files_.totalSize()) };
block_info_ = tr_block_info{ files_.total_size(), default_piece_size(files_.total_size()) };
}
bool tr_metainfo_builder::set_piece_size(uint32_t piece_size) noexcept
@ -144,7 +144,7 @@ bool tr_metainfo_builder::set_piece_size(uint32_t piece_size) noexcept
return false;
}
block_info_ = tr_block_info{ files_.totalSize(), piece_size };
block_info_ = tr_block_info{ files_.total_size(), piece_size };
return true;
}

View file

@ -122,12 +122,12 @@ public:
[[nodiscard]] TR_CONSTEXPR20 auto file_count() const noexcept
{
return files_.fileCount();
return files_.file_count();
}
[[nodiscard]] TR_CONSTEXPR20 auto file_size(tr_file_index_t i) const noexcept
{
return files_.fileSize(i);
return files_.file_size(i);
}
[[nodiscard]] constexpr auto is_private() const noexcept
@ -167,7 +167,7 @@ public:
[[nodiscard]] constexpr auto total_size() const noexcept
{
return files_.totalSize();
return files_.total_size();
}
[[nodiscard]] constexpr auto const& webseeds() const noexcept

View file

@ -489,7 +489,7 @@ void saveProgress(tr_variant* dict, tr_torrent const* tor, tr_torrent::ResumeHel
* pieces cleared from the bitset.
*
* Second approach (2.20 - 3.00): the 'progress' dict had a
* 'time_checked' entry which was a list with fileCount items.
* 'time_checked' entry which was a list with file_count items.
* Each item was either a list of per-piece timestamps, or a
* single timestamp if either all or none of the pieces had been
* tested more recently than the file's mtime.

View file

@ -32,15 +32,15 @@ namespace
using file_func_t = std::function<void(char const* filename)>;
bool isFolder(std::string_view path)
bool is_folder(std::string_view path)
{
auto const info = tr_sys_path_get_info(path);
return info && info->isFolder();
}
bool isEmptyFolder(char const* path)
bool is_empty_folder(char const* path)
{
if (!isFolder(path))
if (!is_folder(path))
{
return false;
}
@ -63,9 +63,9 @@ bool isEmptyFolder(char const* path)
return true;
}
void depthFirstWalk(char const* path, file_func_t const& func, std::optional<int> max_depth = {})
void depth_first_walk(char const* path, file_func_t const& func, std::optional<int> max_depth = {})
{
if (isFolder(path) && (!max_depth || *max_depth > 0))
if (is_folder(path) && (!max_depth || *max_depth > 0))
{
if (auto const odir = tr_sys_dir_open(path); odir != TR_BAD_SYS_DIR)
{
@ -78,7 +78,7 @@ void depthFirstWalk(char const* path, file_func_t const& func, std::optional<int
continue;
}
depthFirstWalk(tr_pathbuf{ path, '/', name }.c_str(), func, max_depth ? *max_depth - 1 : max_depth);
depth_first_walk(tr_pathbuf{ path, '/', name }.c_str(), func, max_depth ? *max_depth - 1 : max_depth);
}
tr_sys_dir_close(odir);
@ -88,7 +88,7 @@ void depthFirstWalk(char const* path, file_func_t const& func, std::optional<int
func(path);
}
bool isJunkFile(std::string_view filename)
bool is_junk_file(std::string_view filename)
{
auto const base = tr_sys_path_basename(filename);
@ -141,9 +141,9 @@ std::optional<tr_torrent_files::FoundFile> tr_torrent_files::find(
return {};
}
bool tr_torrent_files::hasAnyLocalData(std::string_view const* paths, size_t n_paths) const
bool tr_torrent_files::has_any_local_data(std::string_view const* paths, size_t n_paths) const
{
for (tr_file_index_t i = 0, n = fileCount(); i < n; ++i)
for (tr_file_index_t i = 0, n = file_count(); i < n; ++i)
{
if (find(i, paths, n_paths))
{
@ -180,7 +180,7 @@ bool tr_torrent_files::move(
auto err = bool{};
for (tr_file_index_t i = 0, n = fileCount(); i < n; ++i)
for (tr_file_index_t i = 0, n = file_count(); i < n; ++i)
{
auto const found = find(i, std::data(paths), std::size(paths));
if (!found)
@ -210,7 +210,7 @@ bool tr_torrent_files::move(
{
auto const remove_empty_directories = [](char const* filename)
{
if (isEmptyFolder(filename))
if (is_empty_folder(filename))
{
tr_sys_path_remove(filename, nullptr);
}
@ -249,7 +249,7 @@ void tr_torrent_files::remove(std::string_view parent_in, std::string_view tmpdi
// move the local data to the tmpdir
auto const paths = std::array<std::string_view, 1>{ parent.sv() };
for (tr_file_index_t idx = 0, n_files = fileCount(); idx < n_files; ++idx)
for (tr_file_index_t idx = 0, n_files = file_count(); idx < n_files; ++idx)
{
if (auto const found = find(idx, std::data(paths), std::size(paths)); found)
{
@ -261,7 +261,7 @@ void tr_torrent_files::remove(std::string_view parent_in, std::string_view tmpdi
// because we'll need it below in the 'remove junk' phase
auto const path = tr_pathbuf{ parent, '/', tmpdir_prefix };
auto top_files = std::set<std::string>{ std::string{ path } };
depthFirstWalk(
depth_first_walk(
tmpdir,
[&parent, &tmpdir, &top_files](char const* filename)
{
@ -285,8 +285,8 @@ void tr_torrent_files::remove(std::string_view parent_in, std::string_view tmpdi
// the folder hierarchy by removing top-level files & folders first.
// But that can fail -- e.g. `func` might refuse to remove nonempty
// directories -- so plan B is to remove everything bottom-up.
depthFirstWalk(tmpdir, func_wrapper, 1);
depthFirstWalk(tmpdir, func_wrapper);
depth_first_walk(tmpdir, func_wrapper, 1);
depth_first_walk(tmpdir, func_wrapper);
tr_sys_path_remove(tmpdir);
// OK we've removed the local data.
@ -294,23 +294,23 @@ void tr_torrent_files::remove(std::string_view parent_in, std::string_view tmpdi
// Remove the first two categories and leave the third alone.
auto const remove_junk = [](char const* filename)
{
if (isEmptyFolder(filename) || isJunkFile(filename))
if (is_empty_folder(filename) || is_junk_file(filename))
{
tr_sys_path_remove(filename);
}
};
for (auto const& filename : top_files)
{
depthFirstWalk(filename.c_str(), remove_junk);
depth_first_walk(filename.c_str(), remove_junk);
}
}
namespace
{
// `isUnixReservedFile` and `isWin32ReservedFile` kept as `maybe_unused`
// `is_unix_reserved_file` and `is_win32_reserved_file` kept as `maybe_unused`
// for potential support of different filesystems on the same OS
[[nodiscard, maybe_unused]] bool isUnixReservedFile(std::string_view in) noexcept
[[nodiscard, maybe_unused]] bool is_unix_reserved_file(std::string_view in) noexcept
{
static auto constexpr ReservedNames = std::array<std::string_view, 2>{
"."sv,
@ -325,7 +325,7 @@ namespace
// COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9.
// Also avoid these names followed immediately by an extension;
// for example, NUL.txt is not recommended.
[[nodiscard, maybe_unused]] bool isWin32ReservedFile(std::string_view in) noexcept
[[nodiscard, maybe_unused]] bool is_win32_reserved_file(std::string_view in) noexcept
{
if (std::empty(in))
{
@ -365,18 +365,22 @@ namespace
[in_upper_sv](auto const& prefix) { return tr_strv_starts_with(in_upper_sv, prefix); });
}
[[nodiscard]] bool isReservedFile(std::string_view in) noexcept
[[nodiscard]] bool is_reserved_file(std::string_view in, bool os_specific) noexcept
{
if (!os_specific)
{
return is_unix_reserved_file(in) || is_win32_reserved_file(in);
}
#ifdef _WIN32
return isWin32ReservedFile(in);
return is_win32_reserved_file(in);
#else
return isUnixReservedFile(in);
return is_unix_reserved_file(in);
#endif
}
// `isUnixReservedChar` and `isWin32ReservedChar` kept as `maybe_unused`
// `is_unix_reserved_char` and `is_win32_reserved_char` kept as `maybe_unused`
// for potential support of different filesystems on the same OS
[[nodiscard, maybe_unused]] auto constexpr isUnixReservedChar(unsigned char ch) noexcept
[[nodiscard, maybe_unused]] auto constexpr is_unix_reserved_char(unsigned char ch) noexcept
{
return ch == '/';
}
@ -385,7 +389,7 @@ namespace
// Use any character in the current code page for a name, including Unicode
// characters and characters in the extended character set (128255),
// except for the following:
[[nodiscard, maybe_unused]] auto constexpr isWin32ReservedChar(unsigned char ch) noexcept
[[nodiscard, maybe_unused]] auto constexpr is_win32_reserved_char(unsigned char ch) noexcept
{
switch (ch)
{
@ -404,17 +408,21 @@ namespace
}
}
[[nodiscard]] auto constexpr isReservedChar(unsigned char ch) noexcept
[[nodiscard]] auto constexpr is_reserved_char(unsigned char ch, bool os_specific) noexcept
{
if (!os_specific)
{
return is_unix_reserved_char(ch) || is_win32_reserved_char(ch);
}
#ifdef _WIN32
return isWin32ReservedChar(ch);
return is_win32_reserved_char(ch);
#else
return isUnixReservedChar(ch);
return is_unix_reserved_char(ch);
#endif
}
// https://en.wikipedia.org/wiki/Filename#Comparison_of_filename_limitations
void appendSanitizedComponent(std::string_view in, tr_pathbuf& out)
void append_sanitized_component(std::string_view in, tr_pathbuf& out, bool os_specific)
{
#ifdef _WIN32
// remove leading and trailing spaces
@ -428,27 +436,27 @@ void appendSanitizedComponent(std::string_view in, tr_pathbuf& out)
#endif
// replace reserved filenames with an underscore
if (isReservedFile(in))
if (is_reserved_file(in, os_specific))
{
out.append('_');
}
// replace reserved characters with an underscore
static auto constexpr AddChar = [](auto ch)
auto const add_char = [os_specific](auto ch)
{
return isReservedChar(ch) ? '_' : ch;
return is_reserved_char(ch, os_specific) ? '_' : ch;
};
std::transform(std::begin(in), std::end(in), std::back_inserter(out), AddChar);
std::transform(std::begin(in), std::end(in), std::back_inserter(out), add_char);
}
} // namespace
void tr_torrent_files::makeSubpathPortable(std::string_view path, tr_pathbuf& append_me)
void tr_torrent_files::sanitize_subpath(std::string_view path, tr_pathbuf& append_me, bool os_specific)
{
auto segment = std::string_view{};
while (tr_strv_sep(&path, &segment, '/'))
{
appendSanitizedComponent(segment, append_me);
append_sanitized_component(segment, append_me, os_specific);
append_me.append('/');
}

View file

@ -35,17 +35,17 @@ public:
return std::empty(files_);
}
[[nodiscard]] TR_CONSTEXPR20 size_t fileCount() const noexcept
[[nodiscard]] TR_CONSTEXPR20 size_t file_count() const noexcept
{
return std::size(files_);
}
[[nodiscard]] TR_CONSTEXPR20 uint64_t fileSize(tr_file_index_t file_index) const
[[nodiscard]] TR_CONSTEXPR20 uint64_t file_size(tr_file_index_t file_index) const
{
return files_.at(file_index).size_;
}
[[nodiscard]] constexpr auto totalSize() const noexcept
[[nodiscard]] constexpr auto total_size() const noexcept
{
return total_size_;
}
@ -55,12 +55,12 @@ public:
return files_.at(file_index).path_;
}
void setPath(tr_file_index_t file_index, std::string_view path)
void set_path(tr_file_index_t file_index, std::string_view path)
{
files_.at(file_index).setPath(path);
files_.at(file_index).set_path(path);
}
void insertSubpathPrefix(std::string_view path)
void insert_subpath_prefix(std::string_view path)
{
auto const buf = tr_pathbuf{ path, '/' };
@ -76,7 +76,7 @@ public:
files_.reserve(n_files);
}
void shrinkToFit()
void shrink_to_fit()
{
files_.shrink_to_fit();
}
@ -87,7 +87,7 @@ public:
total_size_ = uint64_t{};
}
[[nodiscard]] auto sortedByPath() const
[[nodiscard]] auto sorted_by_path() const
{
auto ret = std::vector<std::pair<std::string /*path*/, uint64_t /*size*/>>{};
ret.reserve(std::size(files_));
@ -153,20 +153,20 @@ public:
};
[[nodiscard]] std::optional<FoundFile> find(tr_file_index_t file, std::string_view const* paths, size_t n_paths) const;
[[nodiscard]] bool hasAnyLocalData(std::string_view const* paths, size_t n_paths) const;
[[nodiscard]] bool has_any_local_data(std::string_view const* paths, size_t n_paths) const;
static void makeSubpathPortable(std::string_view path, tr_pathbuf& append_me);
static void sanitize_subpath(std::string_view path, tr_pathbuf& append_me, bool os_specific = true);
[[nodiscard]] static auto makeSubpathPortable(std::string_view path)
[[nodiscard]] static auto sanitize_subpath(std::string_view path, bool os_specific = true)
{
auto tmp = tr_pathbuf{};
makeSubpathPortable(path, tmp);
sanitize_subpath(path, tmp, os_specific);
return std::string{ tmp.sv() };
}
[[nodiscard]] static bool isSubpathPortable(std::string_view path)
[[nodiscard]] static bool is_subpath_sanitized(std::string_view path, bool os_specific = true)
{
return makeSubpathPortable(path) == path;
return sanitize_subpath(path, os_specific) == path;
}
static constexpr std::string_view PartialFileSuffix = ".part";
@ -175,7 +175,7 @@ private:
struct file_t
{
public:
void setPath(std::string_view subpath)
void set_path(std::string_view subpath)
{
if (path_ != subpath)
{

View file

@ -99,7 +99,7 @@ struct MetainfoHandler final : public transmission::benc::BasicHandler<MaxBencDe
{
file_subpath_ += '/';
}
tr_torrent_files::makeSubpathPortable(currentKey(), file_subpath_);
tr_torrent_files::sanitize_subpath(currentKey(), file_subpath_);
}
else if (pathIs(InfoKey))
{
@ -298,7 +298,7 @@ struct MetainfoHandler final : public transmission::benc::BasicHandler<MaxBencDe
{
file_subpath_ += '/';
}
tr_torrent_files::makeSubpathPortable(value, file_subpath_);
tr_torrent_files::sanitize_subpath(value, file_subpath_);
}
else if (current_key == AttrKey)
{
@ -488,10 +488,10 @@ private:
}
auto root = tr_pathbuf{};
tr_torrent_files::makeSubpathPortable(tm_.name_, root);
tr_torrent_files::sanitize_subpath(tm_.name_, root);
if (!std::empty(root))
{
tm_.files_.insertSubpathPrefix(root);
tm_.files_.insert_subpath_prefix(root);
}
TR_ASSERT(info_dict_begin_[0] == 'd');
@ -522,7 +522,7 @@ private:
// In the single file case, length maps to the length of the file in bytes.
if (tm_.file_count() == 0 && length_ != 0 && !std::empty(tm_.name_))
{
tm_.files_.add(tr_torrent_files::makeSubpathPortable(tm_.name_), length_);
tm_.files_.add(tr_torrent_files::sanitize_subpath(tm_.name_), length_);
}
if (auto const has_metainfo = tm_.info_dict_size() != 0U; has_metainfo)
@ -546,7 +546,7 @@ private:
return false;
}
tm_.block_info_ = tr_block_info{ tm_.files_.totalSize(), piece_size_ };
tm_.block_info_ = tr_block_info{ tm_.files_.total_size(), piece_size_ };
return true;
}

View file

@ -44,11 +44,11 @@ public:
}
[[nodiscard]] TR_CONSTEXPR20 auto file_count() const noexcept
{
return files().fileCount();
return files().file_count();
}
[[nodiscard]] TR_CONSTEXPR20 auto file_size(tr_file_index_t i) const
{
return files().fileSize(i);
return files().file_size(i);
}
[[nodiscard]] TR_CONSTEXPR20 auto const& file_subpath(tr_file_index_t i) const
{
@ -57,7 +57,7 @@ public:
void set_file_subpath(tr_file_index_t i, std::string_view subpath)
{
files_.setPath(i, subpath);
files_.set_path(i, subpath);
}
/// BLOCK INFO

View file

@ -1212,7 +1212,7 @@ bool tr_torrent::has_any_local_data() const
auto paths = std::array<std::string_view, 4>{};
auto const n_paths = buildSearchPathArray(this, std::data(paths));
return files().hasAnyLocalData(std::data(paths), n_paths);
return files().has_any_local_data(std::data(paths), n_paths);
}
void tr_torrentSetDownloadDir(tr_torrent* tor, char const* path)
@ -2548,7 +2548,7 @@ tr_bitfield const& tr_torrent::ResumeHelper::checked_pieces() const noexcept
return tor_.checked_pieces_;
}
void tr_torrent::ResumeHelper::load_checked_pieces(tr_bitfield const& checked, time_t const* mtimes /*fileCount()*/)
void tr_torrent::ResumeHelper::load_checked_pieces(tr_bitfield const& checked, time_t const* mtimes /*file_count()*/)
{
TR_ASSERT(std::size(checked) == tor_.piece_count());
tor_.checked_pieces_ = checked;

View file

@ -70,7 +70,7 @@ struct tr_torrent
class ResumeHelper
{
public:
void load_checked_pieces(tr_bitfield const& checked, time_t const* mtimes /*fileCount()*/);
void load_checked_pieces(tr_bitfield const& checked, time_t const* mtimes /*file_count()*/);
void load_blocks(tr_bitfield blocks);
void load_date_added(time_t when) noexcept;
void load_date_done(time_t when) noexcept;

View file

@ -219,7 +219,7 @@ OSStatus GeneratePreviewForURL(void* /*thisInterface*/, QLPreviewRequestRef prev
FileTreeNode root{};
for (auto const& [path, size] : metainfo.files().sortedByPath())
for (auto const& [path, size] : metainfo.files().sorted_by_path())
{
FileTreeNode* curNode = &root;
size_t level = 0;

View file

@ -84,7 +84,7 @@ protected:
EXPECT_EQ(builder.total_size(), metainfo.total_size());
for (size_t i = 0, n = std::min(builder.file_count(), metainfo.file_count()); i < n; ++i)
{
EXPECT_EQ(builder.file_size(i), metainfo.files().fileSize(i));
EXPECT_EQ(builder.file_size(i), metainfo.files().file_size(i));
EXPECT_EQ(builder.path(i), metainfo.files().path(i));
}
EXPECT_EQ(builder.name(), metainfo.name());

View file

@ -176,7 +176,7 @@ protected:
{
auto paths = std::set<std::string>{};
for (tr_file_index_t i = 0, n = files.fileCount(); i < n; ++i)
for (tr_file_index_t i = 0, n = files.file_count(); i < n; ++i)
{
auto walk = tr_pathbuf{ parent, '/', files.path(i) };
createFileWithContents(walk, std::data(Content), std::size(Content));
@ -332,7 +332,7 @@ TEST_F(RemoveTest, PreservesDirectoryHierarchyIfPossible)
// after remove, the subtree should be:
expected_tree = { parent, recycle_bin.c_str() };
for (tr_file_index_t i = 0, n = files.fileCount(); i < n; ++i)
for (tr_file_index_t i = 0, n = files.file_count(); i < n; ++i)
{
expected_tree.emplace(tr_pathbuf{ recycle_bin, '/', files.path(i) });
}

View file

@ -32,13 +32,13 @@ TEST_F(TorrentFilesTest, add)
auto constexpr Size = size_t{ 1024 };
auto files = tr_torrent_files{};
EXPECT_EQ(size_t{ 0U }, files.fileCount());
EXPECT_EQ(size_t{ 0U }, files.file_count());
EXPECT_TRUE(std::empty(files));
auto const file_index = files.add(Path, Size);
EXPECT_EQ(tr_file_index_t{ 0U }, file_index);
EXPECT_EQ(size_t{ 1U }, files.fileCount());
EXPECT_EQ(Size, files.fileSize(file_index));
EXPECT_EQ(size_t{ 1U }, files.file_count());
EXPECT_EQ(Size, files.file_size(file_index));
EXPECT_EQ(Path, files.path(file_index));
EXPECT_FALSE(std::empty(files));
}
@ -52,11 +52,11 @@ TEST_F(TorrentFilesTest, setPath)
auto files = tr_torrent_files{};
auto const file_index = files.add(Path1, Size);
EXPECT_EQ(Path1, files.path(file_index));
EXPECT_EQ(Size, files.fileSize(file_index));
EXPECT_EQ(Size, files.file_size(file_index));
files.setPath(file_index, Path2);
files.set_path(file_index, Path2);
EXPECT_EQ(Path2, files.path(file_index));
EXPECT_EQ(Size, files.fileSize(file_index));
EXPECT_EQ(Size, files.file_size(file_index));
}
TEST_F(TorrentFilesTest, clear)
@ -67,13 +67,13 @@ TEST_F(TorrentFilesTest, clear)
auto files = tr_torrent_files{};
files.add(Path1, Size);
EXPECT_EQ(size_t{ 1U }, files.fileCount());
EXPECT_EQ(size_t{ 1U }, files.file_count());
files.add(Path2, Size);
EXPECT_EQ(size_t{ 2U }, files.fileCount());
EXPECT_EQ(size_t{ 2U }, files.file_count());
files.clear();
EXPECT_TRUE(std::empty(files));
EXPECT_EQ(size_t{ 0U }, files.fileCount());
EXPECT_EQ(size_t{ 0U }, files.file_count());
}
TEST_F(TorrentFilesTest, find)
@ -135,10 +135,10 @@ TEST_F(TorrentFilesTest, hasAnyLocalData)
auto const search_path_2 = tr_pathbuf{ "/tmp"sv };
auto search_path = std::vector<std::string_view>{ search_path_1.sv(), search_path_2.sv() };
EXPECT_TRUE(files.hasAnyLocalData(std::data(search_path), 2U));
EXPECT_TRUE(files.hasAnyLocalData(std::data(search_path), 1U));
EXPECT_FALSE(files.hasAnyLocalData(std::data(search_path) + 1, 1U));
EXPECT_FALSE(files.hasAnyLocalData(std::data(search_path), 0U));
EXPECT_TRUE(files.has_any_local_data(std::data(search_path), 2U));
EXPECT_TRUE(files.has_any_local_data(std::data(search_path), 1U));
EXPECT_FALSE(files.has_any_local_data(std::data(search_path) + 1, 1U));
EXPECT_FALSE(files.has_any_local_data(std::data(search_path), 0U));
}
TEST_F(TorrentFilesTest, isSubpathPortable)
@ -179,6 +179,6 @@ TEST_F(TorrentFilesTest, isSubpathPortable)
for (auto const& [subpath, expected] : Tests)
{
EXPECT_EQ(expected, tr_torrent_files::isSubpathPortable(subpath)) << " subpath " << subpath;
EXPECT_EQ(expected, tr_torrent_files::is_subpath_sanitized(subpath)) << " subpath " << subpath;
}
}

View file

@ -217,11 +217,11 @@ int tr_main(int argc, char* argv[])
for (tr_file_index_t i = 0; i < n_files; ++i)
{
auto const& path = builder.path(i);
if (!tr_torrent_files::isSubpathPortable(path))
if (!tr_torrent_files::is_subpath_sanitized(path, false))
{
fmt::print(stderr, "WARNING\n");
fmt::print(stderr, "filename \"{:s}\" may not be portable on all systems.\n", path);
fmt::print(stderr, "consider \"{:s}\" instead.\n", tr_torrent_files::makeSubpathPortable(path));
fmt::print(stderr, "consider \"{:s}\" instead.\n", tr_torrent_files::sanitize_subpath(path, false));
}
}