84 lines
2.8 KiB
C++
84 lines
2.8 KiB
C++
|
// This file Copyright © 2019-2022 Mnemosyne LLC.
|
||
|
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||
|
// or any future license endorsed by Mnemosyne LLC.
|
||
|
// License text can be found in the licenses/ folder.
|
||
|
|
||
|
#include <algorithm>
|
||
|
#include <iterator>
|
||
|
#include <string>
|
||
|
#include <string_view>
|
||
|
|
||
|
#include <event2/util.h> // evutil_ascii_strncasecmp
|
||
|
|
||
|
#include "file-info.h"
|
||
|
#include "utils.h"
|
||
|
|
||
|
using namespace std::literals;
|
||
|
|
||
|
static void appendSanitizedComponent(std::string& out, std::string_view in)
|
||
|
{
|
||
|
// remove leading spaces
|
||
|
auto constexpr leading_test = [](unsigned char ch)
|
||
|
{
|
||
|
return isspace(ch);
|
||
|
};
|
||
|
auto const it = std::find_if_not(std::begin(in), std::end(in), leading_test);
|
||
|
in.remove_prefix(std::distance(std::begin(in), it));
|
||
|
|
||
|
// remove trailing spaces and '.'
|
||
|
auto constexpr trailing_test = [](unsigned char ch)
|
||
|
{
|
||
|
return (isspace(ch) != 0) || ch == '.';
|
||
|
};
|
||
|
auto const rit = std::find_if_not(std::rbegin(in), std::rend(in), trailing_test);
|
||
|
in.remove_suffix(std::distance(std::rbegin(in), rit));
|
||
|
|
||
|
// munge banned characters
|
||
|
// https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file
|
||
|
auto constexpr ensure_legal_char = [](auto ch)
|
||
|
{
|
||
|
auto constexpr Banned = std::string_view{ "<>:\"/\\|?*" };
|
||
|
auto const banned = Banned.find(ch) != std::string_view::npos || (unsigned char)ch < 0x20;
|
||
|
return banned ? '_' : ch;
|
||
|
};
|
||
|
auto const old_out_len = std::size(out);
|
||
|
std::transform(std::begin(in), std::end(in), std::back_inserter(out), ensure_legal_char);
|
||
|
|
||
|
// munge banned filenames
|
||
|
// https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file
|
||
|
auto constexpr ReservedNames = std::array<std::string_view, 22>{
|
||
|
"CON"sv, "PRN"sv, "AUX"sv, "NUL"sv, "COM1"sv, "COM2"sv, "COM3"sv, "COM4"sv, "COM5"sv, "COM6"sv, "COM7"sv,
|
||
|
"COM8"sv, "COM9"sv, "LPT1"sv, "LPT2"sv, "LPT3"sv, "LPT4"sv, "LPT5"sv, "LPT6"sv, "LPT7"sv, "LPT8"sv, "LPT9"sv,
|
||
|
};
|
||
|
for (auto const& name : ReservedNames)
|
||
|
{
|
||
|
size_t const name_len = std::size(name);
|
||
|
if (evutil_ascii_strncasecmp(out.c_str() + old_out_len, std::data(name), name_len) != 0 ||
|
||
|
(out[old_out_len + name_len] != '\0' && out[old_out_len + name_len] != '.'))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
out.insert(std::begin(out) + old_out_len + name_len, '_');
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::string tr_file_info::sanitizePath(std::string_view in)
|
||
|
{
|
||
|
auto out = std::string{};
|
||
|
|
||
|
auto segment = std::string_view{};
|
||
|
while (tr_strvSep(&in, &segment, '/'))
|
||
|
{
|
||
|
appendSanitizedComponent(out, segment);
|
||
|
out += '/';
|
||
|
}
|
||
|
if (!std::empty(out)) // remove trailing slash
|
||
|
{
|
||
|
out.resize(std::size(out) - 1);
|
||
|
}
|
||
|
|
||
|
return out;
|
||
|
}
|