transmission/libtransmission/variant-benc.cc

364 lines
8.9 KiB
C++
Raw Normal View History

// This file Copyright © 2008-2023 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.
2023-07-08 15:24:03 +00:00
#include <algorithm>
#include <array>
#include <cctype> /* isdigit() */
2023-07-08 15:24:03 +00:00
#include <cstddef> // size_t, std::byte
#include <cstdint> // int64_t
#include <deque>
2023-07-08 15:24:03 +00:00
#include <string>
#include <string_view>
#include <optional>
#include <fmt/core.h>
#include <fmt/compile.h>
#define LIBTRANSMISSION_VARIANT_MODULE
#include "libtransmission/benc.h"
#include "libtransmission/quark.h"
#include "libtransmission/tr-buffer.h"
#include "libtransmission/utils.h"
#include "libtransmission/variant.h"
2023-07-08 15:24:03 +00:00
struct tr_error;
using namespace std::literals;
auto constexpr MaxBencStrLength = size_t{ 128 * 1024 * 1024 }; // arbitrary
2017-05-16 18:37:00 +00:00
// ---
namespace transmission::benc::impl
{
/**
* The initial i and trailing e are beginning and ending delimiters.
* You can have negative numbers such as i-3e. You cannot prefix the
* number with a zero such as i04e. However, i0e is valid.
* Example: i3e represents the integer "3"
*
* The maximum number of bit of this integer is unspecified,
* but to handle it as a signed 64bit integer is mandatory to handle
* "large files" aka .torrent for more that 4Gbyte
*/
std::optional<int64_t> ParseInt(std::string_view* benc)
{
auto constexpr Prefix = "i"sv;
auto constexpr Suffix = "e"sv;
// find the beginning delimiter
auto walk = *benc;
if (std::size(walk) < 3 || !tr_strv_starts_with(walk, Prefix))
{
return {};
}
2012-12-14 16:04:44 +00:00
// find the ending delimiter
walk.remove_prefix(std::size(Prefix));
if (auto const pos = walk.find(Suffix); pos == std::string_view::npos)
{
return {};
}
2012-12-14 16:04:44 +00:00
// leading zeroes are not allowed
if ((walk[0] == '0' && (isdigit(static_cast<unsigned char>(walk[1])) != 0)) ||
(walk[0] == '-' && walk[1] == '0' && (isdigit(static_cast<unsigned char>(walk[2])) != 0)))
{
return {};
}
// parse the string and make sure the next char is `Suffix`
auto const value = tr_num_parse<int64_t>(walk, &walk);
if (!value || !tr_strv_starts_with(walk, Suffix))
{
return {};
}
walk.remove_prefix(std::size(Suffix));
*benc = walk;
return *value;
}
/**
* Byte strings are encoded as follows:
* <string length encoded in base ten ASCII>:<string data>
* Note that there is no constant beginning delimiter, and no ending delimiter.
* Example: 4:spam represents the string "spam"
*/
std::optional<std::string_view> ParseString(std::string_view* benc)
{
// find the ':' delimiter
auto const colon_pos = benc->find(':');
2021-12-17 05:47:51 +00:00
if (colon_pos == std::string_view::npos)
{
return {};
}
// get the string length
auto svtmp = benc->substr(0, colon_pos);
if (!std::all_of(std::begin(svtmp), std::end(svtmp), [](auto ch) { return isdigit(static_cast<unsigned char>(ch)) != 0; }))
{
return {};
}
auto const len = tr_num_parse<size_t>(svtmp, &svtmp);
if (!len || *len >= MaxBencStrLength)
{
return {};
}
// do we have `len` bytes of string data?
svtmp = benc->substr(colon_pos + 1);
if (std::size(svtmp) < len)
{
return {};
}
auto const string = svtmp.substr(0, *len);
*benc = svtmp.substr(*len);
return string;
}
} // namespace transmission::benc::impl
// ---
namespace
{
namespace parse_helpers
{
struct MyHandler : public transmission::benc::Handler
{
tr_variant* const top_;
2023-08-17 16:02:45 +00:00
bool inplace_;
std::deque<tr_variant*> stack_;
std::optional<tr_quark> key_;
2023-08-17 16:02:45 +00:00
MyHandler(tr_variant* top, bool inplace)
: top_{ top }
2023-08-17 16:02:45 +00:00
, inplace_{ inplace }
{
}
MyHandler(MyHandler&&) = delete;
MyHandler(MyHandler const&) = delete;
MyHandler& operator=(MyHandler&&) = delete;
MyHandler& operator=(MyHandler const&) = delete;
~MyHandler() override = default;
bool Int64(int64_t value, Context const& /*context*/) final
{
auto* const variant = get_node();
if (variant == nullptr)
{
return false;
}
tr_variantInitInt(variant, value);
return true;
}
bool String(std::string_view sv, Context const& /*context*/) final
{
auto* const variant = get_node();
if (variant == nullptr)
{
return false;
}
2023-08-17 16:02:45 +00:00
if (inplace_)
{
tr_variantInitStrView(variant, sv);
}
else
{
tr_variantInitStr(variant, sv);
}
return true;
}
bool StartDict(Context const& /*context*/) final
{
auto* const variant = get_node();
if (variant == nullptr)
{
return false;
}
tr_variantInitDict(variant, 0);
stack_.push_back(variant);
return true;
}
bool Key(std::string_view sv, Context const& /*context*/) final
{
key_ = tr_quark_new(sv);
return true;
}
bool EndDict(Context const& /*context*/) final
{
if (std::empty(stack_))
{
return false;
}
stack_.pop_back();
return true;
}
bool StartArray(Context const& /*context*/) final
{
auto* const variant = get_node();
if (variant == nullptr)
{
return false;
}
tr_variantInitList(variant, 0);
stack_.push_back(variant);
return true;
}
bool EndArray(Context const& /*context*/) final
{
if (std::empty(stack_))
{
return false;
}
stack_.pop_back();
return true;
}
fix: sonarcloud warnings / code smells (#2242) * fix: use-init-statement Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX1f6EvHJiycnfA7gfrG\&open\=AX1f6EvHJiycnfA7gfrG * fix replace-use-of-new warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX1ZNs41ZmlvCveKTzon\&open\=AX1ZNs41ZmlvCveKTzon * fix has-virtual-functions-but-non-virtual-destructor warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX1ZNs72ZmlvCveKTzo6\&open\=AX1ZNs72ZmlvCveKTzo6 * fix make-variable-const warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0_a_OVNJn7rAzml_7B\&open\=AX0_a_OVNJn7rAzml_7B * fix remove-redundant-static-specifiers Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX06St-81usi2gyYkPTb\&open\=AX06St-81usi2gyYkPTb * fix function-should-be-declared-const Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX06St-81usi2gyYkPTd\&open\=AX06St-81usi2gyYkPTd * fix use-init-statement warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX06St-81usi2gyYkPTc\&open\=AX06St-81usi2gyYkPTc * fix class-has-virtual-functions-but-non-virtual-destructor warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX06Stz41usi2gyYkPTS\&open\=AX06Stz41usi2gyYkPTS * fix remove-commented-out-code warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX06St241usi2gyYkPTT\&open\=AX06St241usi2gyYkPTT * fix remove-commented-out-code warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX06St241usi2gyYkPTV\&open\=AX06St241usi2gyYkPTV * fix has-virtual-functions-but-non-virtual-destructor warning https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX06St241usi2gyYkPTW\&open\=AX06St241usi2gyYkPTW * fix remove-commented-out-code warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX06SuCA1usi2gyYkPTh\&open\=AX06SuCA1usi2gyYkPTh * fix use-init-statement warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0rAQvnfJ-O-YIDS9xF\&open\=AX0rAQvnfJ-O-YIDS9xF * fix use-init-statement warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0rAQvnfJ-O-YIDS9xG\&open\=AX0rAQvnfJ-O-YIDS9xG * fix remove-redundant-access-specifier warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX1ZNs5tZmlvCveKTzor\&open\=AX1ZNs5tZmlvCveKTzor * fix use-init-statement warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX01Itl7f_SST5i7BN1l\&open\=AX01Itl7f_SST5i7BN1l * fix use-init-statement warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0wDijI2l89lDvp1C9P\&open\=AX0wDijI2l89lDvp1C9P * fix use-automatically-managed-memory-instead-of-new warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX1f6E6HJiycnfA7gfrI\&open\=AX1f6E6HJiycnfA7gfrI * fix use-init-statement warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0l8vknEafnvRiIHUEv\&open\=AX0l8vknEafnvRiIHUEv * fixup! fix has-virtual-functions-but-non-virtual-destructor warning
2021-11-28 01:58:35 +00:00
private:
tr_variant* get_node()
{
tr_variant* node = nullptr;
if (std::empty(stack_))
{
node = top_;
}
else if (auto* parent = stack_.back(); tr_variantIsList(parent))
{
node = tr_variantListAdd(parent);
}
else if (key_ && tr_variantIsDict(parent))
{
node = tr_variantDictAdd(parent, *key_);
key_.reset();
}
return node;
}
};
} // namespace parse_helpers
} // namespace
2023-08-17 16:02:45 +00:00
std::optional<tr_variant> tr_variant_serde::parse_benc(std::string_view input)
{
using namespace parse_helpers;
using Stack = transmission::benc::ParserStack<512>;
2023-08-17 16:02:45 +00:00
auto top = tr_variant{};
auto stack = Stack{};
2023-08-17 16:02:45 +00:00
auto handler = MyHandler{ &top, parse_inplace_ };
if (transmission::benc::parse(input, stack, handler, &end_, &error_) && std::empty(stack))
{
return std::optional<tr_variant>{ std::move(top) };
2023-08-17 16:02:45 +00:00
}
return {};
}
// ---
namespace
{
namespace to_string_helpers
{
using OutBuf = libtransmission::StackBuffer<1024U * 8U, std::byte>;
2023-08-17 16:02:45 +00:00
void saveIntFunc(tr_variant const& /*var*/, int64_t const val, void* vout)
{
auto out = static_cast<OutBuf*>(vout);
auto const [buf, buflen] = out->reserve_space(64U);
auto* walk = reinterpret_cast<char*>(buf);
auto const* const begin = walk;
2023-08-17 16:02:45 +00:00
walk = fmt::format_to(walk, FMT_COMPILE("i{:d}e"), val);
out->commit_space(walk - begin);
}
2023-08-17 16:02:45 +00:00
void saveBoolFunc(tr_variant const& /*var*/, bool const val, void* vout)
{
2023-08-17 16:02:45 +00:00
static_cast<OutBuf*>(vout)->add(val ? "i1e"sv : "i0e"sv);
}
void saveStringImpl(OutBuf* out, std::string_view sv)
{
// `${sv.size()}:${sv}`
auto const [buf, buflen] = out->reserve_space(std::size(sv) + 32U);
auto* begin = reinterpret_cast<char*>(buf);
auto* const end = fmt::format_to(begin, FMT_COMPILE("{:d}:{:s}"), std::size(sv), sv);
out->commit_space(end - begin);
}
2023-08-17 16:02:45 +00:00
void saveStringFunc(tr_variant const& /*var*/, std::string_view const val, void* vout)
{
2023-08-17 16:02:45 +00:00
saveStringImpl(static_cast<OutBuf*>(vout), val);
}
fix: Coverity warnings (#842) * Silence coverity CHECKED_RETURN on added.f load The existing code behaved alright since added.f is optional. However, by testing for success we can both silence the warning and prevent a useless initialization of NULL/0 to added_f and added_f_length. * Silence coverity CHECKED_RETURN on added6.f load ipv6 variant of previous commit. * Silence coverity CHECKED_RETURN writing benc strs saveStringFunc() gets the target string by calling tr_variantGetStr(). It previously didn't check to see if this function succeeded because saveStringFunc() isn't reached without the type already being known. However, checking the return value costs nothing and makes Coverity happy. * Silence coverity CHECKED_RETURN on ut metadata Like earlier few Coverity commits in this PR, we're handling optional values by declaring stack locals set to the default (e.g. -1) and then trying to read the variant. Unlike the earlier commits, there is a two-part step to thise read: checking for the metadata, then checking for the individual fields. The earlier fixes' aproach -- e.g. initializing to -1 only if the reads failed -- would involve new nested conditionals. I find the new complexity to outweigh the benefit of removing the dead store, so in this case I'm casting the return value to `(void)` to tell Coverity to shush. * Silence coverity CHECKED_RETURN on scrape Check the return value of tr_variantGetInt() when showing seeder and leecher counts in transmission-show. * Silence CHECKED_RETURN on rpc recently-active When building a list of removed torrent IDs from variants, confirm that we can read the IDs from the variants before adding them to the list. I don't _think_ this would have failed before, but Coverity's right that it's reasonable to add a safeguard here. * fix: better fix to serializing benc strings The approach in 33e2ece7e5bc261566ae9e8db57be0b3247508d1 was a little problematic: GetString() shouldn't fail here; but if it somehow did, we still want to encode a zero-length benc string here. * chore: make uncrustify happy
2019-02-18 22:38:24 +00:00
2023-08-17 16:02:45 +00:00
void saveRealFunc(tr_variant const& /*val*/, double const val, void* vout)
{
// the benc spec doesn't handle floats; save it as a string.
auto buf = std::array<char, 64>{};
2023-08-17 16:02:45 +00:00
auto const* const out = fmt::format_to(std::data(buf), FMT_COMPILE("{:f}"), val);
saveStringImpl(static_cast<OutBuf*>(vout), { std::data(buf), static_cast<size_t>(out - std::data(buf)) });
}
2023-08-17 16:02:45 +00:00
void saveDictBeginFunc(tr_variant const& /*val*/, void* vbuf)
{
static_cast<OutBuf*>(vbuf)->push_back('d');
}
2023-08-17 16:02:45 +00:00
void saveListBeginFunc(tr_variant const& /*val*/, void* vbuf)
{
static_cast<OutBuf*>(vbuf)->push_back('l');
}
2023-08-17 16:02:45 +00:00
void saveContainerEndFunc(tr_variant const& /*val*/, void* vbuf)
{
static_cast<OutBuf*>(vbuf)->push_back('e');
}
} // namespace to_string_helpers
} // namespace
2023-08-17 16:02:45 +00:00
std::string tr_variant_serde::to_benc_string(tr_variant const& var)
{
using namespace to_string_helpers;
2023-08-17 16:02:45 +00:00
static auto constexpr Funcs = WalkFuncs{
saveIntFunc, //
saveBoolFunc, //
saveRealFunc, //
saveStringFunc, //
saveDictBeginFunc, //
saveListBeginFunc, //
saveContainerEndFunc, //
};
auto buf = OutBuf{};
2023-08-17 16:02:45 +00:00
walk(var, Funcs, &buf, true);
2023-01-28 02:12:09 +00:00
return buf.to_string();
}