[FarGroup/FarManager] master: Do not use forward slashes in NT paths (2878419ab)

0 views
Skip to first unread message

farg...@farmanager.com

unread,
May 26, 2024, 7:45:51 PMMay 26
to farco...@googlegroups.com
Repository : https://github.com/FarGroup/FarManager
On branch : master
Link : https://github.com/FarGroup/FarManager/commit/2878419ab125f45763d289dee225042bc4b474d5

>---------------------------------------------------------------

commit 2878419ab125f45763d289dee225042bc4b474d5
Author: Alex Alabuzhev <alab...@gmail.com>
Date: Mon May 27 00:36:18 2024 +0100

Do not use forward slashes in NT paths


>---------------------------------------------------------------

2878419ab125f45763d289dee225042bc4b474d5
far/cmdline.cpp | 2 +-
far/copy.cpp | 4 +-
far/cvtname.cpp | 2 +-
far/dirmix.cpp | 2 +-
far/fileedit.cpp | 6 +-
far/pathmix.cpp | 159 +++++++++++++++++++++++++++++++++++++++++---------
far/pathmix.hpp | 64 ++++++--------------
far/platform.fs.cpp | 11 ++--
far/plugins.cpp | 2 +-
far/regex_helpers.hpp | 1 +
10 files changed, 164 insertions(+), 89 deletions(-)

diff --git a/far/cmdline.cpp b/far/cmdline.cpp
index 2d69142e3..a4c2d39d3 100644
--- a/far/cmdline.cpp
+++ b/far/cmdline.cpp
@@ -1366,7 +1366,7 @@ bool CommandLine::IntChDir(string_view const CmdLine, bool const ClosePanel, boo

if (IsAbsolutePath(strExpandedDir))
{
- ReplaceSlashToBackslash(strExpandedDir);
+ path::inplace::normalize_separators(strExpandedDir);
SetPanel->SetCurDir(strExpandedDir,true);
return true;
}
diff --git a/far/copy.cpp b/far/copy.cpp
index 5990f001a..bb5865fa1 100644
--- a/far/copy.cpp
+++ b/far/copy.cpp
@@ -352,7 +352,7 @@ static bool CheckNulOrCon(string_view Src)
if (HasPathPrefix(Src))
Src.remove_prefix(4);

- return (starts_with_icase(Src, L"nul"sv) || starts_with_icase(Src, L"con"sv)) && (Src.size() == 3 || (Src.size() > 3 && path::is_separator(Src[3])));
+ return (starts_with_icase(Src, L"nul"sv) || starts_with_icase(Src, L"con"sv)) && (Src.size() == 3 || (Src.size() > 3 && path::get_is_separator(Src)(Src[3])));
}

static string GenerateName(string_view const Name, string_view const Path)
@@ -1170,7 +1170,7 @@ ShellCopy::ShellCopy(
strDestDizPath.clear();
SrcPanel->SaveSelection();
// TODO: Posix - bugbug
- ReplaceSlashToBackslash(strCopyDlgValue);
+ path::inplace::normalize_separators(strCopyDlgValue);
// нужно ли показывать время копирования?
// ***********************************************************************
// **** Здесь все подготовительные операции закончены, можно приступать
diff --git a/far/cvtname.cpp b/far/cvtname.cpp
index ff2075108..5f666f4e1 100644
--- a/far/cvtname.cpp
+++ b/far/cvtname.cpp
@@ -414,7 +414,7 @@ void PrepareDiskPath(string &strPath, bool CheckFullPath)
// elevation not required during cosmetic operation
SCOPED_ACTION(elevation::suppress);

- ReplaceSlashToBackslash(strPath);
+ path::inplace::normalize_separators(strPath);
const auto DoubleSlash = strPath[1] == L'\\';
remove_duplicates(strPath, L'\\');
if(DoubleSlash)
diff --git a/far/dirmix.cpp b/far/dirmix.cpp
index 333fd2c98..b38ff66d3 100644
--- a/far/dirmix.cpp
+++ b/far/dirmix.cpp
@@ -102,7 +102,7 @@ bool FarChDir(string_view const NewDir)
}

AddEndSlash(Directory);
- ReplaceSlashToBackslash(Directory);
+ path::inplace::normalize_separators(Directory);
PrepareDiskPath(Directory, false); // resolving not needed, very slow

const auto PathType = ParsePath(Directory);
diff --git a/far/fileedit.cpp b/far/fileedit.cpp
index 91aeb2a39..e8d05654d 100644
--- a/far/fileedit.cpp
+++ b/far/fileedit.cpp
@@ -2060,8 +2060,7 @@ void FileEditor::SetFileName(const string_view NewFileName)
{
if (!NewFileName.empty())
{
- strFullFileName = ConvertNameToFull(NewFileName);
- ReplaceSlashToBackslash(strFullFileName);
+ strFullFileName = path::normalize_separators(ConvertNameToFull(NewFileName));
strFileName = PointToName(strFullFileName);
return;
}
@@ -2606,8 +2605,7 @@ bool FileEditor::LoadFromCache(EditorPosCache &pc) const
}
else
{
- strCacheName = strFullFileName;
- ReplaceSlashToBackslash(strCacheName);
+ strCacheName = path::normalize_separators(strFullFileName);
}

pc.Clear();
diff --git a/far/pathmix.cpp b/far/pathmix.cpp
index 8321284bf..ed1698838 100644
--- a/far/pathmix.cpp
+++ b/far/pathmix.cpp
@@ -57,6 +57,76 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

//----------------------------------------------------------------------------

+static string_view append_arg_process(string_view Str)
+{
+ const auto Begin = Str.find_first_not_of(path::separators);
+ if (Begin == Str.npos)
+ return {};
+
+ Str.remove_prefix(Begin);
+
+ const auto LastCharPos = Str.find_last_not_of(path::separators);
+ if (LastCharPos == Str.npos)
+ return {};
+
+ Str.remove_suffix(Str.size() - LastCharPos - 1);
+
+ return Str;
+}
+
+path::detail::append_arg::append_arg(string_view const Str):
+ string_view(append_arg_process(Str))
+{
+}
+
+path::detail::append_arg::append_arg(const wchar_t& Char):
+ string_view(&Char, ::contains(separators, Char)? 0 : 1)
+{
+}
+
+void path::detail::append_impl(string& Str, const std::initializer_list<append_arg>& Args)
+{
+ const auto LastCharPos = HasPathPrefix(Str)? Str.find_last_not_of(separator) : Str.find_last_not_of(separators);
+ Str.resize(LastCharPos == string::npos? 0 : LastCharPos + 1);
+
+ const auto TotalSize = std::ranges::fold_left(Args, Str.size() + (Args.size() - 1), [](size_t const Value, const append_arg& Element)
+ {
+ return Value + Element.size();
+ });
+
+ reserve_exp(Str, TotalSize);
+
+ for (const auto& i: Args)
+ {
+ if (!Str.empty() && (!i.empty() || &i + 1 == Args.end()))
+ Str += separators.front();
+
+ Str += i;
+ }
+}
+
+void path::inplace::normalize_separators(string& Path)
+{
+ if (!HasPathPrefix(Path))
+ ReplaceSlashToBackslash(Path);
+}
+
+decltype(path::is_separator)* path::get_is_separator(string_view const Path)
+{
+ return HasPathPrefix(Path)? is_nt_separator : is_separator;
+}
+
+string path::normalize_separators(string Path)
+{
+ inplace::normalize_separators(Path);
+ return Path;
+}
+
+string path::normalize_separators(string_view Path)
+{
+ return normalize_separators(string(Path));
+}
+
string nt_path(string Path)
{
if (Path.empty())
@@ -138,7 +208,7 @@ root_type ParsePath(const string_view Path, size_t* const RootSize, bool* const
{
// \\?\x:(\...)
root_type::win32nt_drive_letter,
- re(RE_PATH_PREFIX(L".\\:") RE_ANY_SLASH_OR_NONE),
+ re(RE_PATH_PREFIX(L".\\:") RE_BACKSLASH_OR_NONE),
},
{
// \\server\share(\...)
@@ -148,20 +218,20 @@ root_type ParsePath(const string_view Path, size_t* const RootSize, bool* const
{
// \\?\unc\server\share(\...)
root_type::unc_remote,
- re(RE_PATH_PREFIX(L"[Uu][Nn][Cc]" RE_BACKSLASH RE_NONE_OF(RE_DOT RE_SPACE RE_SLASHES RE_Q_MARK) RE_NONE_OF(RE_SPACE RE_SLASHES RE_Q_MARK) RE_ZERO_OR_MORE_LAZY RE_BACKSLASH RE_NONE_OF(RE_SLASHES) RE_ONE_OR_MORE_GREEDY) RE_ANY_SLASH_OR_NONE),
+ re(RE_PATH_PREFIX(L"[Uu][Nn][Cc]" RE_BACKSLASH RE_NONE_OF(RE_DOT RE_SPACE RE_SLASHES RE_Q_MARK) RE_NONE_OF(RE_SPACE RE_SLASHES RE_Q_MARK) RE_ZERO_OR_MORE_LAZY RE_BACKSLASH RE_NONE_OF(RE_SLASHES) RE_ONE_OR_MORE_GREEDY) RE_BACKSLASH_OR_NONE),
},
{
// \\?\Volume{UUID}(\...)
root_type::volume,
- re(RE_PATH_PREFIX(L"[Vv][Oo][Ll][Uu][Mm][Ee]" RE_ESCAPE(L"{") RE_ANY_UUID RE_ESCAPE(L"}")) RE_ANY_SLASH_OR_NONE),
+ re(RE_PATH_PREFIX(L"[Vv][Oo][Ll][Uu][Mm][Ee]" RE_ESCAPE(L"{") RE_ANY_UUID RE_ESCAPE(L"}")) RE_BACKSLASH_OR_NONE),
},
{
// \\?\<anything_else>(\...)
root_type::unknown_rootlike,
- re(RE_PATH_PREFIX(L"." RE_ONE_OR_MORE_LAZY) RE_ANY_SLASH_OR_NONE),
+ re(RE_PATH_PREFIX(L"." RE_ONE_OR_MORE_LAZY) RE_BACKSLASH_OR_NONE),
}

-#undef RE_PATH_REFIX
+#undef RE_PATH_PREFIX
};

std::wcmatch Match;
@@ -194,6 +264,14 @@ bool IsAbsolutePath(const string_view Path)
(Type == root_type::drive_letter && (Path.size() > 2 && path::is_separator(Path[2])));
}

+static bool HasPathPrefix(const std::string_view Path)
+{
+ return
+ Path.starts_with("\\\\?\\"sv) ||
+ Path.starts_with("\\\\.\\"sv) ||
+ Path.starts_with("\\??\\"sv);
+}
+
bool HasPathPrefix(const string_view Path)
{
return
@@ -272,7 +350,7 @@ bool IsCurrentDirectory(string_view const Str)

string_view PointToName(string_view const Path)
{
- const auto NameStart = std::ranges::find_if(Path | std::views::reverse, path::is_separator);
+ const auto NameStart = std::ranges::find_if(Path | std::views::reverse, path::get_is_separator(Path));
return Path.substr(Path.crend() - NameStart);
}

@@ -281,10 +359,7 @@ string_view PointToName(string_view const Path)
// строку
string_view PointToFolderNameIfFolder(string_view Path)
{
- while(!Path.empty() && path::is_separator(Path.back()))
- Path.remove_suffix(1);
-
- return PointToName(Path);
+ return PointToName(DeleteEndSlash(Path));
}

std::pair<string_view, string_view> name_ext(string_view const Path)
@@ -314,7 +389,16 @@ static bool LegacyAddEndSlash(char_type* const Path)
LastSeenSeparator = *end;
}

- if (end != Path && path::is_separator(*(end - 1)))
+ std::basic_string_view const PathView(Path, end);
+
+ if (HasPathPrefix(PathView) && PathView.back() != path::separator)
+ {
+ end[0] = path::separator;
+ end[1] = {};
+ return true;
+ }
+
+ if (!PathView.empty() && path::is_separator(PathView.back()))
return true;

end[0] = LastSeenSeparator;
@@ -336,6 +420,12 @@ bool legacy::AddEndSlash(char* const Path)

void AddEndSlash(string& Path)
{
+ if (HasPathPrefix(Path) && Path.back() != path::separator)
+ {
+ Path += path::separator;
+ return;
+ }
+
if (!Path.empty() && path::is_separator(Path.back()))
return;

@@ -350,7 +440,8 @@ void AddEndSlash(string& Path)
string AddEndSlash(string_view const Path)
{
string Result;
- if (!Path.empty() && path::is_separator(Path.back()))
+
+ if (!Path.empty() && path::get_is_separator(Path)(Path.back()))
return Result = Path;

Result.reserve(Path.size() + 1);
@@ -359,21 +450,30 @@ string AddEndSlash(string_view const Path)
return Result;
}

+static auto final_separators_count(string_view const Path)
+{
+ return std::find_if_not(Path.rbegin(), Path.rend(), path::get_is_separator(Path)) - Path.rbegin();
+}
+
void legacy::DeleteEndSlash(wchar_t* const Path)
{
- const auto REnd = std::make_reverse_iterator(Path);
- Path[REnd - std::find_if_not(REnd - std::wcslen(Path), REnd, path::is_separator)] = 0;
+ string_view NewPath = Path;
+ NewPath.remove_suffix(final_separators_count(Path));
+ Path[NewPath.size()] = 0;
}

-void DeleteEndSlash(string &Path)
+void DeleteEndSlash(string& Path)
{
- Path.resize(Path.rend() - std::find_if_not(Path.rbegin(), Path.rend(), path::is_separator));
+ string_view NewPath = Path;
+ NewPath.remove_suffix(final_separators_count(Path));
+ Path.resize(NewPath.size());
}

string_view DeleteEndSlash(string_view Path)
{
- Path.remove_suffix(std::find_if_not(Path.rbegin(), Path.rend(), path::is_separator) - Path.rbegin());
- return Path;
+ auto NewPath = Path;
+ NewPath.remove_suffix(final_separators_count(Path));
+ return NewPath;
}

bool CutToSlash(string_view& Str, bool const RemoveSlash)
@@ -411,10 +511,11 @@ bool CutToParent(string_view& Str)
if (RootLength == Str.size())
return false;

+ const auto IsSeparator = path::get_is_separator(Str);
const auto REnd = Str.crend() - RootLength;
- const auto LastNotSlash = std::find_if_not(Str.crbegin(), REnd, path::is_separator);
- const auto PrevSlash = std::find_if(LastNotSlash, REnd, path::is_separator);
- const auto PrevNotSlash = std::find_if_not(PrevSlash, REnd, path::is_separator);
+ const auto LastNotSlash = std::find_if_not(Str.crbegin(), REnd, IsSeparator);
+ const auto PrevSlash = std::find_if(LastNotSlash, REnd, IsSeparator);
+ const auto PrevNotSlash = std::find_if_not(PrevSlash, REnd, IsSeparator);

const auto NewSize = RootLength + REnd - PrevNotSlash;
if (!NewSize)
@@ -441,13 +542,13 @@ bool ContainsSlash(const string_view Str)

size_t FindSlash(const string_view Str)
{
- const auto SlashPos = std::ranges::find_if(Str, path::is_separator);
+ const auto SlashPos = std::ranges::find_if(Str, path::get_is_separator(Str));
return SlashPos == Str.cend()? string::npos : SlashPos - Str.cbegin();
}

size_t FindLastSlash(const string_view Str)
{
- const auto SlashPos = std::ranges::find_if(Str | std::views::reverse, path::is_separator);
+ const auto SlashPos = std::ranges::find_if(Str | std::views::reverse, path::get_is_separator(Str));
return SlashPos == Str.crend()? string::npos : Str.crend() - SlashPos - 1;
}

@@ -464,7 +565,7 @@ string_view extract_root_device(string_view const Path)
if (!RootSize)
return{};

- return Path.substr(0, RootSize - (path::is_separator(Path[RootSize - 1])? 1 : 0));
+ return Path.substr(0, RootSize - (path::get_is_separator(Path)(Path[RootSize - 1])? 1 : 0));
}

string extract_root_directory(string_view const Path)
@@ -473,7 +574,7 @@ string extract_root_directory(string_view const Path)
if (!RootSize)
return{};

- if (path::is_separator(Path[RootSize - 1]))
+ if (path::get_is_separator(Path)(Path[RootSize - 1]))
return string{ Path.substr(0, RootSize) };

// A fancy way to add a trailing slash
@@ -526,7 +627,7 @@ bool IsRootPath(const string_view Path)
bool PathStartsWith(const string_view Path, const string_view Start)
{
const auto PathPart = DeleteEndSlash(Start);
- return Path.starts_with(PathPart) && (Path.size() == PathPart.size() || path::is_separator(Path[PathPart.size()]));
+ return Path.starts_with(PathPart) && (Path.size() == PathPart.size() || path::get_is_separator(Path)(Path[PathPart.size()]));
}

#ifdef ENABLE_TESTS
@@ -550,6 +651,7 @@ TEST_CASE("path.join")
REQUIRE(path::join(L"foo\\"sv, L'\\', L"\\bar"sv) == L"foo\\bar"sv);
REQUIRE(path::join(L"foo\\"sv, L""sv, L"\\bar"sv) == L"foo\\bar"sv);
REQUIRE(path::join(L"\\\\foo\\\\"sv, L"\\\\bar\\"sv) == L"\\\\foo\\bar"sv);
+ REQUIRE(path::join(L"\\\\?\\foo/"sv, L"bar"sv) == L"\\\\?\\foo/\\bar"sv);
}

TEST_CASE("path.extract_root")
@@ -916,6 +1018,9 @@ TEST_CASE("path.AddEndSlash")
{ L"a\\\\"sv, L"a\\\\"sv },
{ L"a\\b/"sv, L"a\\b/"sv },
{ L"a\\b/c/d"sv, L"a\\b/c/d/"sv },
+ { L"\\\\?\\1"sv, L"\\\\?\\1\\"sv },
+ { L"\\\\?\\1/"sv, L"\\\\?\\1/\\"sv },
+ { L"\\\\?\\1/2"sv, L"\\\\?\\1/2\\"sv },
};

for (const auto& i: Tests)
@@ -949,6 +1054,8 @@ TEST_CASE("path.DeleteEndSlash")
{ L"a\\\\"sv, L"a"sv },
{ L"a\\b/"sv, L"a\\b"sv },
{ L"a\\b/c/d"sv, L"a\\b/c/d"sv },
+ { L"\\??\\1/"sv, L"\\??\\1/"sv },
+ { L"\\??\\1/\\"sv, L"\\??\\1/"sv },
};

for (const auto& i : Tests)
diff --git a/far/pathmix.hpp b/far/pathmix.hpp
index 26accaa96..91805980e 100644
--- a/far/pathmix.hpp
+++ b/far/pathmix.hpp
@@ -56,62 +56,24 @@ namespace path
{
inline constexpr wchar_t separator = L'\\';
inline constexpr auto separators = L"\\/"sv;
+
+ static_assert(separators.front() == separator);
+
+ constexpr bool is_nt_separator(wchar_t x) noexcept { return x == separator; }
constexpr bool is_separator(wchar_t x) noexcept { return contains(separators, x); }

+ decltype(is_separator)* get_is_separator(string_view Path);
+
namespace detail
{
class append_arg: public string_view
{
public:
- explicit append_arg(string_view const Str):
- string_view(process(Str))
- {
- }
-
- explicit append_arg(const wchar_t& Char):
- string_view(&Char, ::contains(separators, Char)? 0 : 1)
- {
- }
-
- private:
- string_view process(string_view Str)
- {
- const auto Begin = Str.find_first_not_of(separators);
- if (Begin == Str.npos)
- return {};
-
- Str.remove_prefix(Begin);
-
- const auto LastCharPos = Str.find_last_not_of(separators);
- if (LastCharPos == Str.npos)
- return {};
-
- Str.remove_suffix(Str.size() - LastCharPos - 1);
-
- return Str;
- }
+ explicit append_arg(string_view Str);
+ explicit append_arg(const wchar_t& Char);
};

- inline void append_impl(string& Str, const std::initializer_list<append_arg>& Args)
- {
- const auto LastCharPos = Str.find_last_not_of(separators);
- Str.resize(LastCharPos == string::npos? 0 : LastCharPos + 1);
-
- const auto TotalSize = std::ranges::fold_left(Args, Str.size() + (Args.size() - 1), [](size_t const Value, const append_arg& Element)
- {
- return Value + Element.size();
- });
-
- reserve_exp(Str, TotalSize);
-
- for (const auto& i: Args)
- {
- if (!Str.empty() && (!i.empty() || &i + 1 == Args.end()))
- Str += separators.front();
-
- Str += i;
- }
- }
+ void append_impl(string& Str, const std::initializer_list<append_arg>& Args);
}

void append(string& Str, auto const&... Args)
@@ -128,6 +90,14 @@ namespace path
detail::append_impl(Str, { detail::append_arg(Args)... });
return Str;
}
+
+ namespace inplace
+ {
+ void normalize_separators(string& Path);
+ }
+
+ string normalize_separators(string Path);
+ string normalize_separators(string_view Path);
}

string nt_path(string_view Path);
diff --git a/far/platform.fs.cpp b/far/platform.fs.cpp
index 71a5e7dcc..a5eb432e4 100644
--- a/far/platform.fs.cpp
+++ b/far/platform.fs.cpp
@@ -373,7 +373,7 @@ namespace os::fs

static find_file_handle FindFirstFileInternal(string_view const Name, find_data& FindData)
{
- if (Name.empty() || path::is_separator(Name.back()))
+ if (Name.empty() || path::get_is_separator(Name)(Name.back()))
return nullptr;

auto Handle = std::make_unique<far_find_file_handle_impl>();
@@ -1756,7 +1756,7 @@ namespace os::fs

// It seems that "it will be added for you" doesn't work for funny names with trailing dots or spaces, so we add it ourselves.

- if (!Directory.empty() && path::is_separator(Directory.back()))
+ if (!Directory.empty() && path::get_is_separator(Directory)(Directory.back()))
return ::SetCurrentDirectory(null_terminated(Directory).c_str()) != FALSE;

return ::SetCurrentDirectory(AddEndSlash(Directory).c_str()) != FALSE;
@@ -1785,8 +1785,7 @@ namespace os::fs
bool set_current_directory(const string_view PathName, const bool Validate)
{
// correct path to our standard
- string strDir(PathName);
- ReplaceSlashToBackslash(strDir);
+ auto strDir = path::normalize_separators(PathName);
bool Root = false;
ParsePath(strDir, nullptr, &Root);
if (Root)
@@ -1984,7 +1983,7 @@ namespace os::fs
const auto strFrom = nt_path(ExistingFileName);
auto strTo = nt_path(NewFileName);

- if (path::is_separator(strTo.back()))
+ if (path::get_is_separator(strTo)(strTo.back()))
{
append(strTo, PointToName(strFrom));
}
@@ -2020,7 +2019,7 @@ namespace os::fs
const auto strFrom = nt_path(ExistingFileName);
auto strTo = nt_path(NewFileName);

- if (path::is_separator(strTo.back()))
+ if (path::get_is_separator(strTo)(strTo.back()))
{
append(strTo, PointToName(strFrom));
}
diff --git a/far/plugins.cpp b/far/plugins.cpp
index 9d5eef39f..274eef20a 100644
--- a/far/plugins.cpp
+++ b/far/plugins.cpp
@@ -480,7 +480,7 @@ void PluginManager::LoadPluginsFromCache()

for (size_t i = 0; ConfigProvider().PlCacheCfg()->EnumPlugins(i, strModuleName); ++i)
{
- ReplaceSlashToBackslash(strModuleName);
+ path::inplace::normalize_separators(strModuleName);

os::fs::find_data FindData;

diff --git a/far/regex_helpers.hpp b/far/regex_helpers.hpp
index 56c14d918..57483b094 100644
--- a/far/regex_helpers.hpp
+++ b/far/regex_helpers.hpp
@@ -75,6 +75,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define RE_SLASHES RE_SLASH RE_BACKSLASH
#define RE_ANY_SLASH RE_ANY_OF(RE_SLASHES)
#define RE_ANY_SLASHES RE_ANY_SLASH RE_ONE_OR_MORE_LAZY
+#define RE_BACKSLASH_OR_NONE RE_NC_GROUP(RE_BACKSLASH RE_OR RE_END)
#define RE_ANY_SLASH_OR_NONE RE_NC_GROUP(RE_ANY_SLASH RE_OR RE_END)
#define RE_ANY_HEX RE_ANY_OF(L"0-9A-Fa-f")
#define RE_ANY_UUID RE_ANY_HEX RE_REPEAT(8) L"-" RE_NC_GROUP(RE_ANY_HEX RE_REPEAT(4) L"-") RE_REPEAT(3) RE_ANY_HEX RE_REPEAT(12)


Reply all
Reply to author
Forward
0 new messages