refactor: use std::string in tr_info (#2384)

This commit is contained in:
Charles Kerr 2022-01-08 17:41:05 -06:00 committed by GitHub
parent 79d244db82
commit 49f2823d6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 164 additions and 170 deletions

View File

@ -50,7 +50,7 @@ std::string tr_buildTorrentFilename(
static std::string getTorrentFilename(tr_session const* session, tr_info const* inf, enum tr_metainfo_basename_format format)
{
return tr_buildTorrentFilename(tr_getTorrentDir(session), inf->name, inf->hashString, format, ".torrent"sv);
return tr_buildTorrentFilename(tr_getTorrentDir(session), inf->name(), inf->infoHashString(), format, ".torrent"sv);
}
/***
@ -160,7 +160,7 @@ static char const* parseFiles(tr_info* inf, tr_variant* files, tr_variant const*
inf->totalSize = 0;
auto root_name = std::string{};
if (!tr_metainfoAppendSanitizedPathComponent(root_name, inf->name))
if (!tr_metainfoAppendSanitizedPathComponent(root_name, inf->name()))
{
return "path";
}
@ -192,7 +192,7 @@ static char const* parseFiles(tr_info* inf, tr_variant* files, tr_variant const*
break;
}
if (!getfile(&inf->files[i].subpath, root_name, path, buf))
if (!getfile(&inf->files[i].subpath_, root_name, path, buf))
{
errstr = "path";
break;
@ -204,7 +204,7 @@ static char const* parseFiles(tr_info* inf, tr_variant* files, tr_variant const*
break;
}
inf->files[i].size = len;
inf->files[i].size_ = len;
inf->totalSize += len;
}
}
@ -212,8 +212,8 @@ static char const* parseFiles(tr_info* inf, tr_variant* files, tr_variant const*
{
inf->isFolder = false;
inf->files.resize(1);
inf->files[0].subpath = root_name;
inf->files[0].size = len;
inf->files[0].subpath_ = root_name;
inf->files[0].size_ = len;
inf->totalSize += len;
}
else
@ -358,25 +358,17 @@ static char const* tr_metainfoParseImpl(
return "info_hash";
}
if (std::size(sv) != std::size(inf->hash))
if (std::size(sv) != std::size(inf->hash_))
{
return "info_hash";
}
(void)memcpy(std::data(inf->hash), std::data(sv), std::size(sv));
tr_sha1_to_string(inf->hash, inf->hashString);
std::copy(std::begin(sv), std::end(sv), reinterpret_cast<char*>(std::data(inf->hash_)));
inf->info_hash_string_ = tr_sha1_to_string(inf->hash_);
// maybe get the display name
if (tr_variantDictFindStrView(d, TR_KEY_display_name, &sv))
{
tr_free(inf->name);
inf->name = tr_strvDup(sv);
}
if (inf->name == nullptr)
{
inf->name = tr_strdup(inf->hashString);
}
tr_variantDictFindStrView(d, TR_KEY_display_name, &sv);
inf->setName(!std::empty(sv) ? sv : inf->info_hash_string_);
}
else // not a magnet link and has no info dict...
{
@ -392,8 +384,8 @@ static char const* tr_metainfoParseImpl(
return "hash";
}
inf->hash = *hash;
tr_sha1_to_string(inf->hash, inf->hashString);
inf->hash_ = *hash;
inf->info_hash_string_ = tr_sha1_to_string(inf->hash_);
if (info_dict_size != nullptr)
{
@ -415,8 +407,7 @@ static char const* tr_metainfoParseImpl(
return "name";
}
tr_free(inf->name);
inf->name = tr_utf8clean(sv);
inf->name_ = tr_strvUtf8Clean(sv);
}
/* comment */
@ -425,8 +416,7 @@ static char const* tr_metainfoParseImpl(
sv = ""sv;
}
tr_free(inf->comment);
inf->comment = tr_utf8clean(sv);
inf->comment_ = tr_strvUtf8Clean(sv);
/* created by */
if (!tr_variantDictFindStrView(meta, TR_KEY_created_by_utf_8, &sv) &&
@ -435,13 +425,12 @@ static char const* tr_metainfoParseImpl(
sv = ""sv;
}
tr_free(inf->creator);
inf->creator = tr_utf8clean(sv);
inf->creator_ = tr_strvUtf8Clean(sv);
/* creation date */
i = 0;
(void)!tr_variantDictFindInt(meta, TR_KEY_creation_date, &i);
inf->dateCreated = i;
inf->date_created_ = i;
/* private */
if (!tr_variantDictFindInt(infoDict, TR_KEY_private, &i) && !tr_variantDictFindInt(meta, TR_KEY_private, &i))
@ -457,8 +446,7 @@ static char const* tr_metainfoParseImpl(
sv = ""sv;
}
tr_free(inf->source);
inf->source = tr_utf8clean(sv);
inf->source_ = tr_strvUtf8Clean(sv);
/* piece length */
if (!isMagnet)
@ -520,8 +508,7 @@ static char const* tr_metainfoParseImpl(
geturllist(inf, meta);
/* filename of Transmission's copy */
tr_free(inf->torrent);
inf->torrent = session != nullptr ? tr_strvDup(getTorrentFilename(session, inf, TR_METAINFO_BASENAME_HASH)) : nullptr;
inf->torrent_file_ = session != nullptr ? getTorrentFilename(session, inf, TR_METAINFO_BASENAME_HASH) : ""sv;
return nullptr;
}
@ -551,18 +538,8 @@ void tr_metainfoFree(tr_info* inf)
}
}
tr_free(inf->comment);
tr_free(inf->creator);
tr_free(inf->name);
tr_free(inf->source);
tr_free(inf->torrent);
tr_free(inf->webseeds);
inf->comment = nullptr;
inf->creator = nullptr;
inf->name = nullptr;
inf->source = nullptr;
inf->torrent = nullptr;
inf->webseeds = nullptr;
inf->announce_list.reset();
@ -589,7 +566,7 @@ void tr_metainfoMigrateFile(
if (tr_sys_path_rename(old_filename.c_str(), new_filename.c_str(), nullptr))
{
tr_logAddNamedError(
info->name,
info->name().c_str(),
"Migrated torrent file from \"%s\" to \"%s\"",
old_filename.c_str(),
new_filename.c_str());

View File

@ -484,12 +484,7 @@ static void addPeers(tr_torrent const* tor, tr_variant* list)
tr_torrentPeersFree(peers, peerCount);
}
static void initField(
tr_torrent* const tor,
tr_torrent_view const* view,
tr_stat const* const st,
tr_variant* const initme,
tr_quark key)
static void initField(tr_torrent* const tor, tr_stat const* const st, tr_variant* const initme, tr_quark key)
{
char* str = nullptr;
@ -508,7 +503,7 @@ static void initField(
break;
case TR_KEY_comment:
tr_variantInitStr(initme, std::string_view{ view->comment != nullptr ? view->comment : "" });
tr_variantInitStr(initme, tor->comment());
break;
case TR_KEY_corruptEver:
@ -516,11 +511,11 @@ static void initField(
break;
case TR_KEY_creator:
tr_variantInitStr(initme, std::string_view{ view->creator != nullptr ? view->creator : "" });
tr_variantInitStrView(initme, tor->creator());
break;
case TR_KEY_dateCreated:
tr_variantInitInt(initme, view->date_created);
tr_variantInitInt(initme, tor->dateCreated());
break;
case TR_KEY_desiredAvailable:
@ -692,11 +687,11 @@ static void initField(
break;
case TR_KEY_pieceCount:
tr_variantInitInt(initme, view->n_pieces);
tr_variantInitInt(initme, tor->pieceCount());
break;
case TR_KEY_pieceSize:
tr_variantInitInt(initme, view->piece_size);
tr_variantInitInt(initme, tor->pieceSize());
break;
case TR_KEY_primary_mime_type:
@ -755,7 +750,7 @@ static void initField(
break;
case TR_KEY_source:
tr_variantDictAddStr(initme, key, view->source ? view->source : "");
tr_variantInitStrView(initme, tor->source());
break;
case TR_KEY_startDate:
@ -792,11 +787,11 @@ static void initField(
}
case TR_KEY_torrentFile:
tr_variantInitStr(initme, view->torrent_filename);
tr_variantInitStrView(initme, tor->torrentFile());
break;
case TR_KEY_totalSize:
tr_variantInitInt(initme, view->total_size);
tr_variantInitInt(initme, tor->totalSize());
break;
case TR_KEY_uploadedEver:
@ -853,14 +848,13 @@ static void addTorrentInfo(tr_torrent* tor, tr_format format, tr_variant* entry,
if (fieldCount > 0)
{
auto const torrent_view = tr_torrentView(tor);
tr_stat const* const st = tr_torrentStat(tor);
for (size_t i = 0; i < fieldCount; ++i)
{
tr_variant* child = format == TR_FORMAT_TABLE ? tr_variantListAdd(entry) : tr_variantDictAdd(entry, fields[i]);
initField(tor, &torrent_view, st, child, fields[i]);
initField(tor, st, child, fields[i]);
}
}
}

View File

@ -6,6 +6,7 @@
*
*/
#include <algorithm>
#include <climits> /* INT_MAX */
#include <cstring> /* memcpy(), memset(), memcmp() */
#include <ctime>
@ -168,7 +169,7 @@ void* tr_torrentGetMetadataPiece(tr_torrent* tor, int piece, size_t* len)
return nullptr;
}
auto const fd = tr_sys_file_open(tor->torrentFile(), TR_SYS_FILE_READ, 0, nullptr);
auto const fd = tr_sys_file_open(tor->torrentFile().c_str(), TR_SYS_FILE_READ, 0, nullptr);
if (fd == TR_BAD_SYS_FILE)
{
return nullptr;
@ -261,7 +262,7 @@ void tr_torrentSetMetadataPiece(tr_torrent* tor, int piece, void const* data, in
}
size_t const offset = piece * METADATA_PIECE_SIZE;
memcpy(m->metadata + offset, data, len);
std::copy_n(reinterpret_cast<char const*>(data), len, m->metadata + offset);
tr_removeElementFromArray(m->piecesNeeded, idx, sizeof(struct metadata_node), m->piecesNeededCount);
--m->piecesNeededCount;
@ -288,15 +289,15 @@ void tr_torrentSetMetadataPiece(tr_torrent* tor, int piece, void const* data, in
{
/* yay we have bencoded metainfo... merge it into our .torrent file */
tr_variant newMetainfo;
char* path = tr_strdup(tor->torrentFile());
auto const path = tor->torrentFile();
if (tr_variantFromFile(&newMetainfo, TR_VARIANT_PARSE_BENC, path, nullptr))
{
/* remove any old .torrent and .resume files */
tr_sys_path_remove(path, nullptr);
tr_sys_path_remove(path.c_str(), nullptr);
tr_torrentRemoveResume(tor);
dbgmsg(tor, "Saving completed metadata to \"%s\"", path);
dbgmsg(tor, "Saving completed metadata to \"%s\"", path.c_str());
tr_variantMergeDicts(tr_variantDictAddDict(&newMetainfo, TR_KEY_info, 0), &infoDict);
auto info = tr_metainfoParse(tor->session, &newMetainfo, nullptr);
@ -322,7 +323,6 @@ void tr_torrentSetMetadataPiece(tr_torrent* tor, int piece, void const* data, in
}
tr_variantFree(&infoDict);
tr_free(path);
}
}
@ -394,10 +394,10 @@ char* tr_torrentInfoGetMagnetLink(tr_info const* inf)
auto buf = std::string{};
buf += "magnet:?xt=urn:btih:"sv;
buf += inf->hashString;
buf += inf->infoHashString();
char const* const name = inf->name;
if (!tr_str_is_empty(name))
auto const& name = inf->name();
if (!std::empty(name))
{
buf += "&dn="sv;
tr_http_escape(buf, name, true);

View File

@ -77,7 +77,7 @@ using namespace std::literals;
char const* tr_torrentName(tr_torrent const* tor)
{
return tor != nullptr ? tor->info.name : "";
return tor != nullptr ? tor->name().c_str() : "";
}
uint64_t tr_torrentTotalSize(tr_torrent const* tor)
@ -727,7 +727,7 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
tr_sessionAddTorrent(session, tor);
/* if we don't have a local .torrent file already, assume the torrent is new */
bool const isNewTorrent = !tr_sys_path_exists(tor->torrentFile(), nullptr);
bool const isNewTorrent = !tr_sys_path_exists(tor->torrentFile().c_str(), nullptr);
/* maybe save our own copy of the metainfo */
if (tr_ctorGetSave(ctor))
@ -787,7 +787,7 @@ tr_torrent* tr_torrentNew(tr_ctor const* ctor, tr_torrent** setme_duplicate_of)
}
// is it a duplicate?
if (auto* const duplicate_of = session->getTorrent(parsed->info.hash); duplicate_of != nullptr)
if (auto* const duplicate_of = session->getTorrent(parsed->info.infoHash()); duplicate_of != nullptr)
{
if (setme_duplicate_of != nullptr)
{
@ -1158,13 +1158,13 @@ tr_torrent_view tr_torrentView(tr_torrent const* tor)
auto ret = tr_torrent_view{};
ret.name = tr_torrentName(tor);
ret.hash_string = tor->infoHashString();
ret.torrent_filename = tor->torrentFile();
ret.comment = tor->info.comment;
ret.creator = tor->info.creator;
ret.source = tor->info.source;
ret.hash_string = tor->infoHashString().c_str();
ret.torrent_filename = tor->torrentFile().c_str();
ret.comment = tor->info.comment().c_str();
ret.creator = tor->info.creator().c_str();
ret.source = tor->info.source().c_str();
ret.total_size = tor->totalSize();
ret.date_created = tor->info.dateCreated;
ret.date_created = tor->dateCreated();
ret.piece_size = tor->pieceSize();
ret.n_pieces = tor->pieceCount();
ret.is_private = tor->isPrivate();
@ -1983,7 +1983,7 @@ bool tr_torrentReqIsValid(tr_torrent const* tor, tr_piece_index_t index, uint32_
}
// TODO(ckerr) migrate to fpm?
tr_block_span_t tr_torGetFileBlockSpan(tr_torrent const* tor, tr_file_index_t const i)
tr_block_span_t tr_torGetFileBlockSpan(tr_torrent const* tor, tr_file_index_t i)
{
auto const [begin_byte, end_byte] = tor->fpm_.byteSpan(i);
@ -3114,16 +3114,10 @@ void tr_torrent::setBlocks(tr_bitfield blocks)
void tr_torrent::setName(std::string_view name)
{
if (name == this->info.name)
{
return;
}
tr_free(this->info.name);
this->info.name = tr_strvDup(name);
this->info.setName(name);
}
void tr_torrent::setFileSubpath(tr_file_index_t i, std::string_view subpath)
{
this->info.files[i].subpath = subpath;
this->info.setFileSubpath(i, subpath);
}

View File

@ -86,7 +86,7 @@ void tr_torrentGetBlockLocation(
uint32_t* offset,
uint32_t* length);
tr_block_span_t tr_torGetFileBlockSpan(tr_torrent const* tor, tr_file_index_t const file);
tr_block_span_t tr_torGetFileBlockSpan(tr_torrent const* tor, tr_file_index_t file);
void tr_torrentCheckSeedLimit(tr_torrent* tor);
@ -449,9 +449,14 @@ public:
void setName(std::string_view name);
[[nodiscard]] auto const& name() const
{
return this->info.name();
}
[[nodiscard]] auto const& infoHash() const
{
return this->info.hash;
return this->info.infoHash();
}
[[nodiscard]] auto isPrivate() const
@ -464,14 +469,34 @@ public:
return !this->isPrivate();
}
[[nodiscard]] auto infoHashString() const
[[nodiscard]] auto const& infoHashString() const
{
return this->info.hashString;
return this->info.infoHashString();
}
[[nodiscard]] auto dateCreated() const
{
return this->info.dateCreated();
}
[[nodiscard]] auto const& torrentFile() const
{
return this->info.torrent;
return this->info.torrentFile();
}
[[nodiscard]] auto const& comment() const
{
return this->info.comment();
}
[[nodiscard]] auto const& creator() const
{
return this->info.creator();
}
[[nodiscard]] auto const& source() const
{
return this->info.source();
}
[[nodiscard]] auto hasMetadata() const

View File

@ -276,7 +276,8 @@ int tr_lpdInit(tr_session* ss, tr_address* /*tr_addr*/)
* string handling in tr_lpdSendAnnounce() and tr_lpdConsiderAnnounce().
* However, the code should work as long as interfaces to the rest of
* libtransmission are compatible with char* strings. */
static_assert(std::is_same_v<char const, std::remove_pointer_t<decltype(std::declval<tr_torrent>().infoHashString())>>);
static_assert(
std::is_same_v<std::string const&, std::remove_pointer_t<decltype(std::declval<tr_torrent>().infoHashString())>>);
struct ip_mreq mcastReq;
int const opt_on = 1;
@ -456,7 +457,7 @@ bool tr_lpdEnabled(tr_session const* ss)
*/
bool tr_lpdSendAnnounce(tr_torrent const* t)
{
char const fmt[] = //
char constexpr fmt[] = //
"BT-SEARCH * HTTP/%u.%u" CRLF //
"Host: %s:%u" CRLF //
"Port: %u" CRLF //
@ -464,23 +465,17 @@ bool tr_lpdSendAnnounce(tr_torrent const* t)
"" CRLF //
"" CRLF;
char hashString[SIZEOF_HASH_STRING];
char query[lpd_maxDatagramLength + 1] = { 0 };
if (t == nullptr)
{
return false;
}
/* make sure the hash string is normalized, just in case */
auto const* const sourceHashString = t->infoHashString();
for (size_t i = 0; i < TR_N_ELEMENTS(hashString); ++i)
{
hashString[i] = toupper(sourceHashString[i]);
}
/* ensure the hash string is capitalized */
auto const hash_string = tr_strupper(t->infoHashString());
/* prepare a zero-terminated announce message */
tr_snprintf(query, lpd_maxDatagramLength + 1, fmt, 1, 1, lpd_mcastGroup, lpd_mcastPort, lpd_port, hashString);
char query[lpd_maxDatagramLength + 1] = { 0 };
tr_snprintf(query, lpd_maxDatagramLength + 1, fmt, 1, 1, lpd_mcastGroup, lpd_mcastPort, lpd_port, hash_string.c_str());
/* actually send the query out using [lpd_socket2] */
{
@ -488,7 +483,7 @@ bool tr_lpdSendAnnounce(tr_torrent const* t)
/* destination address info has already been set up in tr_lpdInit(),
* so we refrain from preparing another sockaddr_in here */
int res = sendto(lpd_socket2, query, len, 0, (struct sockaddr const*)&lpd_mcastAddr, sizeof(lpd_mcastAddr));
int const res = sendto(lpd_socket2, query, len, 0, (struct sockaddr const*)&lpd_mcastAddr, sizeof(lpd_mcastAddr));
if (res != len)
{

View File

@ -21,6 +21,8 @@
***/
#include <memory>
#include <string_view>
#include <stdbool.h> /* bool */
#include <stddef.h> /* size_t */
#include <stdint.h> /* uintN_t */
@ -1502,8 +1504,8 @@ void tr_torrentVerify(tr_torrent* torrent, tr_verify_done_func callback_func_or_
struct tr_file
{
// public
std::string subpath; /* Path to the file */
uint64_t size; /* Length of the file, in bytes */
std::string subpath_; /* Path to the file */
uint64_t size_; /* Length of the file, in bytes */
};
/** @brief information about a torrent that comes from its metainfo file */
@ -1513,18 +1515,53 @@ struct tr_info
uint64_t totalSize;
/* The torrent's name. */
char* name;
std::string name_;
/* Path to torrent Transmission's internal copy of the .torrent file. */
char* torrent;
std::string torrent_file_;
char** webseeds;
char* comment;
char* creator;
std::string comment_;
std::string creator_;
/* torrent's source. empty if not set. */
char* source;
std::string source_;
auto const& torrentFile() const
{
return torrent_file_;
}
auto const& name() const
{
return name_;
}
auto const& creator() const
{
return creator_;
}
auto const& comment() const
{
return comment_;
}
auto const& source() const
{
return source_;
}
auto const& infoHash() const
{
return hash_;
}
void setName(std::string_view name)
{
name_ = name;
}
// Private.
// Use tr_torrentFile() and tr_torrentFileCount() instead.
@ -1537,27 +1574,42 @@ struct tr_info
std::string const& fileSubpath(tr_file_index_t i) const
{
return files[i].subpath;
return files[i].subpath_;
}
void setFileSubpath(tr_file_index_t i, std::string_view subpath)
{
files[i].subpath_ = subpath;
}
auto fileSize(tr_file_index_t i) const
{
return files[i].size;
return files[i].size_;
}
auto const& infoHashString() const
{
return info_hash_string_;
}
auto dateCreated() const
{
return date_created_;
}
// TODO(ckerr) aggregate this directly, rather than using a shared_ptr, when tr_info is private
std::shared_ptr<tr_announce_list> announce_list;
/* Torrent info */
time_t dateCreated;
time_t date_created_;
unsigned int webseedCount;
uint32_t pieceSize;
tr_piece_index_t pieceCount;
/* General info */
tr_sha1_digest_t hash;
char hashString[2 * SHA_DIGEST_LENGTH + 1];
tr_sha1_digest_t hash_;
std::string info_hash_string_;
/* Flags */
bool isPrivate;

View File

@ -820,13 +820,6 @@ static char* to_utf8(std::string_view sv)
return strip_non_utf8(sv);
}
char* tr_utf8clean(std::string_view sv)
{
char* const ret = tr_utf8_validate(sv, nullptr) ? tr_strvDup(sv) : to_utf8(sv);
TR_ASSERT(tr_utf8_validate(ret, nullptr));
return ret;
}
std::string tr_strvUtf8Clean(std::string_view sv)
{
if (tr_utf8_validate(sv, nullptr))

View File

@ -164,13 +164,6 @@ std::optional<T> tr_parseNum(std::string_view& sv)
#endif // #if defined(__GNUC__) && !__has_include(<charconv>)
/**
* @brief make a copy of 'str' whose non-utf8 content has been corrected or stripped
* @return a newly-allocated string that must be freed with tr_free()
* @param str the string to make a clean copy of
*/
char* tr_utf8clean(std::string_view str) TR_GNUC_MALLOC;
bool tr_utf8_validate(std::string_view sv, char const** endptr);
#ifdef _WIN32
@ -300,6 +293,14 @@ std::string tr_strlower(T in)
return out;
}
template<typename T>
std::string tr_strupper(T in)
{
auto out = std::string{ in };
std::for_each(std::begin(out), std::end(out), [](char& ch) { ch = std::toupper(ch); });
return out;
}
/***
**** std::string_view utils
***/

View File

@ -387,9 +387,9 @@ TEST_F(AnnounceListTest, save)
tr_variantFree(&metainfo);
// test that non-announce parts of the metainfo are the same
EXPECT_STREQ(original->info.name, saved->info.name);
EXPECT_EQ(original->info.name(), saved->info.name());
EXPECT_EQ(original->info.fileCount(), saved->info.fileCount());
EXPECT_EQ(original->info.dateCreated, saved->info.dateCreated);
EXPECT_EQ(original->info.dateCreated(), saved->info.dateCreated());
EXPECT_EQ(original->pieces, saved->pieces);
// test that the saved version has the updated announce list

View File

@ -264,7 +264,6 @@ TEST_F(RenameTest, multifileTorrent)
"MjpwaWVjZSBsZW5ndGhpMzI3NjhlNjpwaWVjZXMyMDp27buFkmy8ICfNX4nsJmt0Ckm2Ljc6cHJp"
"dmF0ZWkwZWVl");
EXPECT_TRUE(tr_isTorrent(tor));
auto& files = tor->info.files;
// sanity check the info
EXPECT_STREQ("Felidae", tr_torrentName(tor));
@ -320,7 +319,7 @@ TEST_F(RenameTest, multifileTorrent)
// (while the branch is renamed: confirm that the .resume file remembers the changes)
tr_torrentSaveResume(tor);
// this is a bit dodgy code-wise, but let's make sure the .resume file got the name
files[1].subpath = "gabba gabba hey";
tor->setFileSubpath(1, "gabba gabba hey"sv);
auto const loaded = tr_torrentLoadResume(tor, ~0ULL, ctor, nullptr);
EXPECT_NE(decltype(loaded){ 0 }, (loaded & TR_FR_FILENAMES));
EXPECT_EQ(expected_files[0], tr_torrentFile(tor, 0).name);

View File

@ -158,42 +158,6 @@ TEST_F(UtilsTest, trStrvPath)
tr_strvPath("foo"sv, "bar", std::string{ "baz" }, "mum"sv));
}
TEST_F(UtilsTest, trUtf8clean)
{
auto in = "hello world"sv;
auto out = makeString(tr_utf8clean(in));
EXPECT_EQ(in, out);
in = "hello world"sv;
out = makeString(tr_utf8clean(in.substr(0, 5)));
EXPECT_EQ("hello"sv, out);
// this version is not utf-8 (but cp866)
in = "\x92\xE0\xE3\xA4\xAD\xAE \xA1\xEB\xE2\xEC \x81\xAE\xA3\xAE\xAC"sv;
out = makeString(tr_utf8clean(in));
EXPECT_TRUE(std::size(out) == 17 || std::size(out) == 33);
EXPECT_TRUE(tr_utf8_validate(out, nullptr));
// same string, but utf-8 clean
in = "Трудно быть Богом"sv;
out = makeString(tr_utf8clean(in));
EXPECT_NE(nullptr, out.data());
EXPECT_TRUE(tr_utf8_validate(out, nullptr));
EXPECT_EQ(in, out);
in = "\xF4\x00\x81\x82"sv;
out = makeString(tr_utf8clean(in));
EXPECT_NE(nullptr, out.data());
EXPECT_TRUE(out.size() == 1 || out.size() == 2);
EXPECT_TRUE(tr_utf8_validate(out, nullptr));
in = "\xF4\x33\x81\x82"sv;
out = makeString(tr_utf8clean(in));
EXPECT_NE(nullptr, out.data());
EXPECT_TRUE(out.size() == 4 || out.size() == 7);
EXPECT_TRUE(tr_utf8_validate(out, nullptr));
}
TEST_F(UtilsTest, trStrvUtf8Clean)
{
auto in = "hello world"sv;