refactor: use std::string_view in benc parser (#2182)
* refactor: use std::string_view to parse benc * refactor: simplify private parsing API in variant.cc * chore: remove unneeded includes * refactor: merge tr_variantFromBenc and tr_variantFromBencFull
This commit is contained in:
parent
60b802a22b
commit
1ba81938bf
|
@ -137,6 +137,18 @@ std::string tr_magnet_metainfo::magnet() const
|
|||
return s;
|
||||
}
|
||||
|
||||
static tr_quark announceToScrape(std::string_view announce)
|
||||
{
|
||||
auto buf = std::string{};
|
||||
|
||||
if (!tr_magnet_metainfo::convertAnnounceToScrape(buf, announce))
|
||||
{
|
||||
return TR_KEY_NONE;
|
||||
}
|
||||
|
||||
return tr_quark_new(buf);
|
||||
}
|
||||
|
||||
bool tr_magnet_metainfo::addTracker(tr_tracker_tier_t tier, std::string_view announce_sv)
|
||||
{
|
||||
announce_sv = tr_strvStrip(announce_sv);
|
||||
|
@ -146,9 +158,8 @@ bool tr_magnet_metainfo::addTracker(tr_tracker_tier_t tier, std::string_view ann
|
|||
return false;
|
||||
}
|
||||
|
||||
auto buf = std::string{};
|
||||
auto const announce_url = tr_quark_new(announce_sv);
|
||||
auto const scrape_url = convertAnnounceToScrape(buf, announce_sv) ? tr_quark_new(buf) : TR_KEY_NONE;
|
||||
auto const scrape_url = announceToScrape(announce_sv);
|
||||
this->trackers.insert({ tier, { announce_url, scrape_url, tier } });
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1241,7 +1241,7 @@ static void parseUtMetadata(tr_peerMsgsImpl* msgs, uint32_t msglen, struct evbuf
|
|||
|
||||
auto dict = tr_variant{};
|
||||
char const* benc_end = nullptr;
|
||||
if (tr_variantFromBencFull(&dict, std::string_view{ tmp, msglen }, &benc_end) == 0)
|
||||
if (tr_variantFromBenc(&dict, std::string_view{ tmp, msglen }, &benc_end) == 0)
|
||||
{
|
||||
(void)tr_variantDictFindInt(&dict, TR_KEY_msg_type, &msg_type);
|
||||
(void)tr_variantDictFindInt(&dict, TR_KEY_piece, &piece);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cctype> /* isdigit() */
|
||||
#include <deque>
|
||||
#include <cerrno>
|
||||
|
@ -23,6 +24,8 @@
|
|||
#include "variant.h"
|
||||
#include "variant-common.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
#define MAX_BENC_STR_LENGTH (128 * 1024 * 1024) /* arbitrary */
|
||||
|
||||
/***
|
||||
|
@ -35,50 +38,45 @@
|
|||
* 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"
|
||||
* NOTE: The maximum number of bit of this integer is unspecified,
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
int tr_bencParseInt(void const* vbuf, void const* vbufend, uint8_t const** setme_end, int64_t* setme_val)
|
||||
std::optional<int64_t> tr_bencParseInt(std::string_view* benc)
|
||||
{
|
||||
uint8_t const* const buf = (uint8_t const*)vbuf;
|
||||
uint8_t const* const bufend = (uint8_t const*)vbufend;
|
||||
|
||||
if (buf >= bufend)
|
||||
// find the beginning delimiter
|
||||
auto walk = *benc;
|
||||
if (std::size(walk) < 3 || walk.front() != 'i')
|
||||
{
|
||||
return EILSEQ;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (*buf != 'i')
|
||||
// find the ending delimiter
|
||||
walk.remove_prefix(1);
|
||||
auto const pos = walk.find('e');
|
||||
if (pos == walk.npos)
|
||||
{
|
||||
return EILSEQ;
|
||||
return {};
|
||||
}
|
||||
|
||||
void const* begin = buf + 1;
|
||||
void const* end = memchr(begin, 'e', (bufend - buf) - 1);
|
||||
|
||||
if (end == nullptr)
|
||||
// leading zeroes are not allowed
|
||||
if ((walk[0] == '0' && isdigit(walk[1])) || (walk[0] == '-' && walk[1] == '0' && isdigit(walk[2])))
|
||||
{
|
||||
return EILSEQ;
|
||||
return {};
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
char* endptr = nullptr;
|
||||
int64_t val = evutil_strtoll(static_cast<char const*>(begin), &endptr, 10);
|
||||
|
||||
if (errno != 0 || endptr != end) /* incomplete parse */
|
||||
auto const value = std::strtoll(std::data(walk), &endptr, 10);
|
||||
if (errno != 0 || endptr != std::data(walk) + pos)
|
||||
{
|
||||
return EILSEQ;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (val != 0 && *(char const*)begin == '0') /* no leading zeroes! */
|
||||
{
|
||||
return EILSEQ;
|
||||
}
|
||||
|
||||
*setme_end = (uint8_t const*)end + 1;
|
||||
*setme_val = val;
|
||||
return 0;
|
||||
walk.remove_prefix(pos + 1);
|
||||
*benc = walk;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,46 +85,36 @@ int tr_bencParseInt(void const* vbuf, void const* vbufend, uint8_t const** setme
|
|||
* Note that there is no constant beginning delimiter, and no ending delimiter.
|
||||
* Example: 4:spam represents the string "spam"
|
||||
*/
|
||||
int tr_bencParseStr(
|
||||
void const* vbuf,
|
||||
void const* vbufend,
|
||||
uint8_t const** setme_end,
|
||||
uint8_t const** setme_str,
|
||||
size_t* setme_strlen)
|
||||
std::optional<std::string_view> tr_bencParseStr(std::string_view* benc)
|
||||
{
|
||||
uint8_t const* const buf = (uint8_t const*)vbuf;
|
||||
uint8_t const* const bufend = (uint8_t const*)vbufend;
|
||||
|
||||
if ((buf < bufend) && isdigit(*buf))
|
||||
// find the ':' delimiter
|
||||
auto const colon_pos = benc->find(':');
|
||||
if (colon_pos == benc->npos)
|
||||
{
|
||||
void const* end = memchr(buf, ':', bufend - buf);
|
||||
|
||||
if (end != nullptr)
|
||||
{
|
||||
errno = 0;
|
||||
char* ulend = nullptr;
|
||||
size_t len = strtoul((char const*)buf, &ulend, 10);
|
||||
|
||||
if (errno == 0 && ulend == end && len <= MAX_BENC_STR_LENGTH)
|
||||
{
|
||||
uint8_t const* strbegin = (uint8_t const*)end + 1;
|
||||
uint8_t const* strend = strbegin + len;
|
||||
|
||||
if (strbegin <= strend && strend <= bufend)
|
||||
{
|
||||
*setme_end = (uint8_t const*)end + 1 + len;
|
||||
*setme_str = (uint8_t const*)end + 1;
|
||||
*setme_strlen = len;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
*setme_end = nullptr;
|
||||
*setme_str = nullptr;
|
||||
*setme_strlen = 0;
|
||||
return EILSEQ;
|
||||
// get the string length
|
||||
errno = 0;
|
||||
char* ulend = nullptr;
|
||||
auto const len = strtoul(std::data(*benc), &ulend, 10);
|
||||
if (errno != 0 || ulend != std::data(*benc) + colon_pos || len >= MAX_BENC_STR_LENGTH)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
// do we have `len` bytes of string data?
|
||||
auto walk = *benc;
|
||||
walk.remove_prefix(colon_pos + 1);
|
||||
if (std::size(walk) < len)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto const string = walk.substr(0, len);
|
||||
walk.remove_prefix(len);
|
||||
*benc = walk;
|
||||
return string;
|
||||
}
|
||||
|
||||
static tr_variant* get_node(std::deque<tr_variant*>& stack, std::optional<tr_quark>& dict_key, tr_variant* top, int* err)
|
||||
|
@ -164,24 +152,17 @@ static tr_variant* get_node(std::deque<tr_variant*>& stack, std::optional<tr_qua
|
|||
* easier to read, but was vulnerable to a smash-stacking
|
||||
* attack via maliciously-crafted bencoded data. (#667)
|
||||
*/
|
||||
int tr_variantParseBenc(void const* buf_in, void const* bufend_in, tr_variant* top, char const** setme_end)
|
||||
int tr_variantParseBenc(tr_variant& top, std::string_view benc, char const** setme_end)
|
||||
{
|
||||
int err = 0;
|
||||
auto const* buf = static_cast<uint8_t const*>(buf_in);
|
||||
auto const* const bufend = static_cast<uint8_t const*>(bufend_in);
|
||||
auto stack = std::deque<tr_variant*>{};
|
||||
auto key = std::optional<tr_quark>{};
|
||||
|
||||
if ((buf_in == nullptr) || (bufend_in == nullptr) || (top == nullptr))
|
||||
{
|
||||
return EINVAL;
|
||||
}
|
||||
tr_variantInit(&top, 0);
|
||||
|
||||
tr_variantInit(top, 0);
|
||||
|
||||
while (buf != bufend)
|
||||
int err = 0;
|
||||
for (;;)
|
||||
{
|
||||
if (buf > bufend) /* no more text to parse... */
|
||||
if (std::empty(benc))
|
||||
{
|
||||
err = EILSEQ;
|
||||
}
|
||||
|
@ -191,90 +172,94 @@ int tr_variantParseBenc(void const* buf_in, void const* bufend_in, tr_variant* t
|
|||
break;
|
||||
}
|
||||
|
||||
if (*buf == 'i') /* int */
|
||||
switch (benc.front())
|
||||
{
|
||||
uint8_t const* end = nullptr;
|
||||
auto val = int64_t{};
|
||||
if ((err = tr_bencParseInt(buf, bufend, &end, &val)) != 0)
|
||||
case 'i': // int
|
||||
{
|
||||
break;
|
||||
}
|
||||
auto const value = tr_bencParseInt(&benc);
|
||||
if (!value)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
buf = end;
|
||||
|
||||
tr_variant* const v = get_node(stack, key, top, &err);
|
||||
if (v != nullptr)
|
||||
{
|
||||
tr_variantInitInt(v, val);
|
||||
}
|
||||
}
|
||||
else if (*buf == 'l') /* list */
|
||||
{
|
||||
++buf;
|
||||
|
||||
tr_variant* const v = get_node(stack, key, top, &err);
|
||||
if (v != nullptr)
|
||||
{
|
||||
tr_variantInitList(v, 0);
|
||||
stack.push_back(v);
|
||||
}
|
||||
}
|
||||
else if (*buf == 'd') /* dict */
|
||||
{
|
||||
++buf;
|
||||
|
||||
tr_variant* const v = get_node(stack, key, top, &err);
|
||||
if (v != nullptr)
|
||||
{
|
||||
tr_variantInitDict(v, 0);
|
||||
stack.push_back(v);
|
||||
}
|
||||
}
|
||||
else if (*buf == 'e') /* end of list or dict */
|
||||
{
|
||||
++buf;
|
||||
|
||||
if (std::empty(stack) || key)
|
||||
{
|
||||
err = EILSEQ;
|
||||
break;
|
||||
}
|
||||
|
||||
stack.pop_back();
|
||||
if (std::empty(stack))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (isdigit(*buf)) /* string? */
|
||||
{
|
||||
uint8_t const* end = nullptr;
|
||||
uint8_t const* str = nullptr;
|
||||
auto str_len = size_t{};
|
||||
if ((err = tr_bencParseStr(buf, bufend, &end, &str, &str_len)) != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
buf = end;
|
||||
auto const sv = std::string_view{ reinterpret_cast<char const*>(str), str_len };
|
||||
|
||||
if (!key && !std::empty(stack) && tr_variantIsDict(stack.back()))
|
||||
{
|
||||
key = tr_quark_new(sv);
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_variant* const v = get_node(stack, key, top, &err);
|
||||
tr_variant* const v = get_node(stack, key, &top, &err);
|
||||
if (v != nullptr)
|
||||
{
|
||||
tr_variantInitStr(v, sv);
|
||||
tr_variantInitInt(v, *value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else /* invalid bencoded text... march past it */
|
||||
{
|
||||
++buf;
|
||||
case 'l': // list
|
||||
{
|
||||
benc.remove_prefix(1);
|
||||
|
||||
tr_variant* const v = get_node(stack, key, &top, &err);
|
||||
if (v != nullptr)
|
||||
{
|
||||
tr_variantInitList(v, 0);
|
||||
stack.push_back(v);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'd': // dict
|
||||
{
|
||||
benc.remove_prefix(1);
|
||||
|
||||
tr_variant* const v = get_node(stack, key, &top, &err);
|
||||
if (v != nullptr)
|
||||
{
|
||||
tr_variantInitDict(v, 0);
|
||||
stack.push_back(v);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'e': // end of list or dict
|
||||
{
|
||||
benc.remove_prefix(1);
|
||||
|
||||
if (std::empty(stack) || key)
|
||||
{
|
||||
err = EILSEQ;
|
||||
break;
|
||||
}
|
||||
|
||||
stack.pop_back();
|
||||
break;
|
||||
}
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9': // string?
|
||||
{
|
||||
auto const sv = tr_bencParseStr(&benc);
|
||||
if (!sv)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!key && !std::empty(stack) && tr_variantIsDict(stack.back()))
|
||||
{
|
||||
key = tr_quark_new(*sv);
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_variant* const v = get_node(stack, key, &top, &err);
|
||||
if (v != nullptr)
|
||||
{
|
||||
tr_variantInitStr(v, *sv);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: // invalid bencoded text... march past it
|
||||
benc.remove_prefix(1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (std::empty(stack))
|
||||
|
@ -283,7 +268,7 @@ int tr_variantParseBenc(void const* buf_in, void const* bufend_in, tr_variant* t
|
|||
}
|
||||
}
|
||||
|
||||
if (err == 0 && (top->type == 0 || !std::empty(stack)))
|
||||
if (err == 0 && (top.type == 0 || !std::empty(stack)))
|
||||
{
|
||||
err = EILSEQ;
|
||||
}
|
||||
|
@ -292,13 +277,13 @@ int tr_variantParseBenc(void const* buf_in, void const* bufend_in, tr_variant* t
|
|||
{
|
||||
if (setme_end != nullptr)
|
||||
{
|
||||
*setme_end = (char const*)buf;
|
||||
*setme_end = std::data(benc);
|
||||
}
|
||||
}
|
||||
else if (top->type != 0)
|
||||
else if (top.type != 0)
|
||||
{
|
||||
tr_variantFree(top);
|
||||
tr_variantInit(top, 0);
|
||||
tr_variantFree(&top);
|
||||
tr_variantInit(&top, 0);
|
||||
}
|
||||
|
||||
return err;
|
||||
|
@ -329,12 +314,12 @@ static void saveBoolFunc(tr_variant const* val, void* vevbuf)
|
|||
|
||||
static void saveRealFunc(tr_variant const* val, void* vevbuf)
|
||||
{
|
||||
char buf[128];
|
||||
int const len = tr_snprintf(buf, sizeof(buf), "%f", val->val.d);
|
||||
auto buf = std::array<char, 64>{};
|
||||
int const len = tr_snprintf(std::data(buf), std::size(buf), "%f", val->val.d);
|
||||
|
||||
auto* evbuf = static_cast<struct evbuffer*>(vevbuf);
|
||||
auto* evbuf = static_cast<evbuffer*>(vevbuf);
|
||||
evbuffer_add_printf(evbuf, "%d:", len);
|
||||
evbuffer_add(evbuf, buf, len);
|
||||
evbuffer_add(evbuf, std::data(buf), len);
|
||||
}
|
||||
|
||||
static void saveStringFunc(tr_variant const* v, void* vevbuf)
|
||||
|
|
|
@ -12,7 +12,10 @@
|
|||
#error only libtransmission/variant-*.c should #include this header.
|
||||
#endif
|
||||
|
||||
#include "tr-macros.h"
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
struct tr_variant;
|
||||
|
||||
using VariantWalkFunc = void (*)(tr_variant const* val, void* user_data);
|
||||
|
||||
|
@ -35,18 +38,12 @@ void tr_variantToBufBenc(tr_variant const* top, struct evbuffer* buf);
|
|||
|
||||
void tr_variantInit(tr_variant* v, char type);
|
||||
|
||||
/* source - such as a filename. Only when logging an error */
|
||||
int tr_jsonParse(char const* source, void const* vbuf, size_t len, tr_variant* setme_benc, char const** setme_end);
|
||||
/** @brief Private function that's exposed here only for unit tests */
|
||||
std::optional<int64_t> tr_bencParseInt(std::string_view* benc_inout);
|
||||
|
||||
/** @brief Private function that's exposed here only for unit tests */
|
||||
int tr_bencParseInt(void const* buf, void const* bufend, uint8_t const** setme_end, int64_t* setme_val);
|
||||
std::optional<std::string_view> tr_bencParseStr(std::string_view* benc_inout);
|
||||
|
||||
/** @brief Private function that's exposed here only for unit tests */
|
||||
int tr_bencParseStr(
|
||||
void const* buf,
|
||||
void const* bufend,
|
||||
uint8_t const** setme_end,
|
||||
uint8_t const** setme_str,
|
||||
size_t* setme_strlen);
|
||||
int tr_variantParseBenc(tr_variant& setme, std::string_view benc, char const** setme_end);
|
||||
|
||||
int tr_variantParseBenc(void const* buf, void const* end, tr_variant* top, char const** setme_end);
|
||||
int tr_variantParseJson(tr_variant& setme, std::string_view benc, char const** setme_end);
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include <cstring>
|
||||
|
||||
#include <event2/buffer.h> /* evbuffer_add() */
|
||||
#include <event2/util.h> /* evutil_strtoll() */
|
||||
|
||||
#define LIBTRANSMISSION_VARIANT_MODULE
|
||||
|
||||
|
@ -40,7 +39,6 @@ struct json_wrapper_data
|
|||
size_t keylen;
|
||||
struct evbuffer* keybuf;
|
||||
struct evbuffer* strbuf;
|
||||
char const* source;
|
||||
std::deque<tr_variant*> stack;
|
||||
|
||||
/* A very common pattern is for a container's children to be similar,
|
||||
|
@ -80,19 +78,7 @@ static void error_handler(jsonsl_t jsn, jsonsl_error_t error, jsonsl_state_st* /
|
|||
{
|
||||
auto* data = static_cast<struct json_wrapper_data*>(jsn->data);
|
||||
|
||||
if (data->source != nullptr)
|
||||
{
|
||||
tr_logAddError(
|
||||
"JSON parse failed in %s at pos %zu: %s -- remaining text \"%.16s\"",
|
||||
data->source,
|
||||
jsn->pos,
|
||||
jsonsl_strerror(error),
|
||||
buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_logAddError("JSON parse failed at pos %zu: %s -- remaining text \"%.16s\"", jsn->pos, jsonsl_strerror(error), buf);
|
||||
}
|
||||
tr_logAddError("JSON parse failed at pos %zu: %s -- remaining text \"%.16s\"", jsn->pos, jsonsl_strerror(error), buf);
|
||||
|
||||
data->error = EILSEQ;
|
||||
}
|
||||
|
@ -334,7 +320,7 @@ static void action_callback_POP(
|
|||
{
|
||||
char const* begin = jsn->base + state->pos_begin;
|
||||
data->has_content = true;
|
||||
tr_variantInitInt(get_node(jsn), evutil_strtoll(begin, nullptr, 10));
|
||||
tr_variantInitInt(get_node(jsn), std::strtoll(begin, nullptr, 10));
|
||||
}
|
||||
else if ((state->special_flags & JSONSL_SPECIALf_BOOLEAN) != 0)
|
||||
{
|
||||
|
@ -350,7 +336,7 @@ static void action_callback_POP(
|
|||
}
|
||||
}
|
||||
|
||||
int tr_jsonParse(char const* source, void const* vbuf, size_t len, tr_variant* setme_variant, char const** setme_end)
|
||||
int tr_variantParseJson(tr_variant& setme, std::string_view benc, char const** setme_end)
|
||||
{
|
||||
auto data = json_wrapper_data{};
|
||||
|
||||
|
@ -364,15 +350,14 @@ int tr_jsonParse(char const* source, void const* vbuf, size_t len, tr_variant* s
|
|||
data.error = 0;
|
||||
data.has_content = false;
|
||||
data.key = nullptr;
|
||||
data.top = setme_variant;
|
||||
data.top = &setme;
|
||||
data.stack = {};
|
||||
data.source = source;
|
||||
data.keybuf = evbuffer_new();
|
||||
data.strbuf = evbuffer_new();
|
||||
data.preallocGuess = {};
|
||||
|
||||
/* parse it */
|
||||
jsonsl_feed(jsn, static_cast<jsonsl_char_t const*>(vbuf), len);
|
||||
jsonsl_feed(jsn, static_cast<jsonsl_char_t const*>(std::data(benc)), std::size(benc));
|
||||
|
||||
/* EINVAL if there was no content */
|
||||
if (data.error == 0 && !data.has_content)
|
||||
|
@ -383,7 +368,7 @@ int tr_jsonParse(char const* source, void const* vbuf, size_t len, tr_variant* s
|
|||
/* maybe set the end ptr */
|
||||
if (setme_end != nullptr)
|
||||
{
|
||||
*setme_end = ((char const*)vbuf) + jsn->pos;
|
||||
*setme_end = std::data(benc) + jsn->pos;
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
|
@ -643,14 +628,10 @@ static void jsonListBeginFunc(tr_variant const* val, void* vdata)
|
|||
static void jsonContainerEndFunc(tr_variant const* val, void* vdata)
|
||||
{
|
||||
auto* data = static_cast<struct jsonWalk*>(vdata);
|
||||
bool emptyContainer = false;
|
||||
|
||||
jsonPopParent(data);
|
||||
|
||||
if (!emptyContainer)
|
||||
{
|
||||
jsonIndent(data);
|
||||
}
|
||||
jsonIndent(data);
|
||||
|
||||
if (tr_variantIsDict(val))
|
||||
{
|
||||
|
|
|
@ -1292,13 +1292,7 @@ int tr_variantToFile(tr_variant const* v, tr_variant_fmt fmt, char const* filena
|
|||
****
|
||||
***/
|
||||
|
||||
static int tr_variantFromBuf(
|
||||
tr_variant* setme,
|
||||
tr_variant_fmt fmt,
|
||||
void const* buf,
|
||||
size_t buflen,
|
||||
char const* optional_source,
|
||||
char const** setme_end)
|
||||
static int tr_variantFromBuf(tr_variant& setme, tr_variant_fmt fmt, std::string_view buf, char const** setme_end)
|
||||
{
|
||||
/* parse with LC_NUMERIC="C" to ensure a "." decimal separator */
|
||||
struct locale_context locale_ctx;
|
||||
|
@ -1309,11 +1303,11 @@ static int tr_variantFromBuf(
|
|||
{
|
||||
case TR_VARIANT_FMT_JSON:
|
||||
case TR_VARIANT_FMT_JSON_LEAN:
|
||||
err = tr_jsonParse(optional_source, buf, buflen, setme, setme_end);
|
||||
err = tr_variantParseJson(setme, buf, setme_end);
|
||||
break;
|
||||
|
||||
default /* TR_VARIANT_FMT_BENC */:
|
||||
err = tr_variantParseBenc(buf, (char const*)buf + buflen, setme, setme_end);
|
||||
err = tr_variantParseBenc(setme, buf, setme_end);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1322,40 +1316,31 @@ static int tr_variantFromBuf(
|
|||
return err;
|
||||
}
|
||||
|
||||
int tr_variantFromBenc(tr_variant* setme, std::string_view benc)
|
||||
int tr_variantFromBenc(tr_variant* setme, std::string_view benc, char const** setme_end)
|
||||
{
|
||||
return tr_variantFromBuf(setme, TR_VARIANT_FMT_BENC, std::data(benc), std::size(benc), nullptr, nullptr);
|
||||
}
|
||||
|
||||
int tr_variantFromBencFull(tr_variant* setme, std::string_view benc, char const** setme_end)
|
||||
{
|
||||
return tr_variantFromBuf(setme, TR_VARIANT_FMT_BENC, std::data(benc), std::size(benc), nullptr, setme_end);
|
||||
return tr_variantFromBuf(*setme, TR_VARIANT_FMT_BENC, benc, setme_end);
|
||||
}
|
||||
|
||||
int tr_variantFromJson(tr_variant* setme, std::string_view json)
|
||||
{
|
||||
return tr_variantFromBuf(setme, TR_VARIANT_FMT_JSON, std::data(json), std::size(json), nullptr, nullptr);
|
||||
return tr_variantFromBuf(*setme, TR_VARIANT_FMT_JSON, json, nullptr);
|
||||
}
|
||||
|
||||
bool tr_variantFromFile(tr_variant* setme, tr_variant_fmt fmt, char const* filename, tr_error** error)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
auto buflen = size_t{};
|
||||
uint8_t* const buf = tr_loadFile(filename, &buflen, error);
|
||||
if (buf != nullptr)
|
||||
auto buf = std::vector<char>{};
|
||||
if (!tr_loadFile(buf, filename, error))
|
||||
{
|
||||
if (tr_variantFromBuf(setme, fmt, buf, buflen, filename, nullptr) == 0)
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_error_set_literal(error, 0, _("Unable to parse file content"));
|
||||
}
|
||||
|
||||
tr_free(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
auto sv = std::string_view{ std::data(buf), std::size(buf) };
|
||||
auto const error_code = tr_variantFromBuf(*setme, fmt, sv, nullptr);
|
||||
if (error_code != 0)
|
||||
{
|
||||
tr_error_set_literal(error, error_code, _("Unable to parse file content"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -115,9 +115,7 @@ struct evbuffer* tr_variantToBuf(tr_variant const* variant, tr_variant_fmt fmt);
|
|||
/* TR_VARIANT_FMT_JSON_LEAN and TR_VARIANT_FMT_JSON are equivalent here. */
|
||||
bool tr_variantFromFile(tr_variant* setme, tr_variant_fmt fmt, char const* filename, struct tr_error** error);
|
||||
|
||||
int tr_variantFromBenc(tr_variant* setme, std::string_view benc);
|
||||
|
||||
int tr_variantFromBencFull(tr_variant* setme, std::string_view benc, char const** setme_end);
|
||||
int tr_variantFromBenc(tr_variant* setme, std::string_view benc, char const** setme_end = nullptr);
|
||||
|
||||
int tr_variantFromJson(tr_variant* setme, std::string_view json);
|
||||
|
||||
|
|
|
@ -34,11 +34,6 @@ protected:
|
|||
s.erase(std::find_if_not(s.rbegin(), s.rend(), ::isspace).base(), s.end());
|
||||
return s;
|
||||
}
|
||||
|
||||
auto bencParseInt(std::string const& in, uint8_t const** end, int64_t* val)
|
||||
{
|
||||
return tr_bencParseInt(in.data(), in.data() + in.size(), end, val);
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef _WIN32
|
||||
|
@ -94,150 +89,120 @@ TEST_F(VariantTest, getType)
|
|||
|
||||
TEST_F(VariantTest, parseInt)
|
||||
{
|
||||
auto const in = std::string{ "i64e" };
|
||||
auto constexpr InitVal = int64_t{ 888 };
|
||||
auto constexpr Benc = "i64e"sv;
|
||||
auto constexpr ExpectVal = int64_t{ 64 };
|
||||
|
||||
uint8_t const* end = {};
|
||||
auto val = int64_t{ InitVal };
|
||||
auto const err = bencParseInt(in, &end, &val);
|
||||
EXPECT_EQ(0, err);
|
||||
EXPECT_EQ(ExpectVal, val);
|
||||
EXPECT_EQ(reinterpret_cast<decltype(end)>(in.data() + in.size()), end);
|
||||
auto benc = Benc;
|
||||
auto const value = tr_bencParseInt(&benc);
|
||||
EXPECT_TRUE(value);
|
||||
EXPECT_EQ(ExpectVal, *value);
|
||||
EXPECT_EQ(std::data(Benc) + std::size(Benc), std::data(benc));
|
||||
}
|
||||
|
||||
TEST_F(VariantTest, parseIntWithMissingEnd)
|
||||
{
|
||||
auto const in = std::string{ "i64" };
|
||||
auto constexpr InitVal = int64_t{ 888 };
|
||||
auto constexpr Benc = "i64"sv;
|
||||
|
||||
uint8_t const* end = {};
|
||||
auto val = int64_t{ InitVal };
|
||||
auto const err = bencParseInt(in, &end, &val);
|
||||
EXPECT_EQ(EILSEQ, err);
|
||||
EXPECT_EQ(InitVal, val);
|
||||
EXPECT_EQ(nullptr, end);
|
||||
auto benc = Benc;
|
||||
EXPECT_FALSE(tr_bencParseInt(&benc));
|
||||
EXPECT_EQ(std::data(Benc), std::data(benc));
|
||||
}
|
||||
|
||||
TEST_F(VariantTest, parseIntEmptyBuffer)
|
||||
{
|
||||
auto const in = std::string{};
|
||||
auto constexpr InitVal = int64_t{ 888 };
|
||||
auto constexpr Benc = ""sv;
|
||||
|
||||
uint8_t const* end = {};
|
||||
auto val = int64_t{ InitVal };
|
||||
auto const err = bencParseInt(in, &end, &val);
|
||||
EXPECT_EQ(EILSEQ, err);
|
||||
EXPECT_EQ(InitVal, val);
|
||||
EXPECT_EQ(nullptr, end);
|
||||
auto benc = Benc;
|
||||
EXPECT_FALSE(tr_bencParseInt(&benc));
|
||||
EXPECT_EQ(std::data(Benc), std::data(benc));
|
||||
}
|
||||
|
||||
TEST_F(VariantTest, parseIntWithBadDigits)
|
||||
{
|
||||
auto const in = std::string{ "i6z4e" };
|
||||
auto constexpr InitVal = int64_t{ 888 };
|
||||
auto constexpr Benc = "i6z4e"sv;
|
||||
|
||||
uint8_t const* end = {};
|
||||
auto val = int64_t{ InitVal };
|
||||
auto const err = bencParseInt(in, &end, &val);
|
||||
EXPECT_EQ(EILSEQ, err);
|
||||
EXPECT_EQ(InitVal, val);
|
||||
EXPECT_EQ(nullptr, end);
|
||||
auto benc = Benc;
|
||||
EXPECT_FALSE(tr_bencParseInt(&benc));
|
||||
EXPECT_EQ(std::data(Benc), std::data(benc));
|
||||
}
|
||||
|
||||
TEST_F(VariantTest, parseNegativeInt)
|
||||
{
|
||||
auto const in = std::string{ "i-3e" };
|
||||
auto constexpr Benc = "i-3e"sv;
|
||||
auto constexpr Expected = int64_t{ -3 };
|
||||
|
||||
uint8_t const* end = {};
|
||||
auto val = int64_t{};
|
||||
auto const err = bencParseInt(in, &end, &val);
|
||||
EXPECT_EQ(0, err);
|
||||
EXPECT_EQ(-3, val);
|
||||
EXPECT_EQ(reinterpret_cast<decltype(end)>(in.data() + in.size()), end);
|
||||
auto benc = Benc;
|
||||
auto const value = tr_bencParseInt(&benc);
|
||||
EXPECT_TRUE(value);
|
||||
EXPECT_EQ(Expected, *value);
|
||||
EXPECT_EQ(std::data(Benc) + std::size(Benc), std::data(benc));
|
||||
}
|
||||
|
||||
TEST_F(VariantTest, parseNegativeWithLeadingZero)
|
||||
{
|
||||
auto constexpr Benc = "i-03e"sv;
|
||||
|
||||
auto benc = Benc;
|
||||
EXPECT_FALSE(tr_bencParseInt(&benc));
|
||||
EXPECT_EQ(std::data(Benc), std::data(benc));
|
||||
}
|
||||
|
||||
TEST_F(VariantTest, parseIntZero)
|
||||
{
|
||||
auto const in = std::string{ "i0e" };
|
||||
auto constexpr Benc = "i0e"sv;
|
||||
auto constexpr Expected = int64_t{ 0 };
|
||||
|
||||
uint8_t const* end = {};
|
||||
auto val = int64_t{};
|
||||
auto const err = bencParseInt(in, &end, &val);
|
||||
EXPECT_EQ(0, err);
|
||||
EXPECT_EQ(0, val);
|
||||
EXPECT_EQ(reinterpret_cast<decltype(end)>(in.data() + in.size()), end);
|
||||
auto benc = Benc;
|
||||
auto const value = tr_bencParseInt(&benc);
|
||||
EXPECT_TRUE(value);
|
||||
EXPECT_EQ(Expected, *value);
|
||||
EXPECT_EQ(std::data(Benc) + std::size(Benc), std::data(benc));
|
||||
}
|
||||
|
||||
TEST_F(VariantTest, parseIntWithLeadingZero)
|
||||
{
|
||||
auto const in = std::string{ "i04e" };
|
||||
auto constexpr InitVal = int64_t{ 888 };
|
||||
auto constexpr Benc = "i04e"sv;
|
||||
|
||||
uint8_t const* end = {};
|
||||
auto val = int64_t{ InitVal };
|
||||
auto const err = bencParseInt(in, &end, &val);
|
||||
EXPECT_EQ(EILSEQ, err); // no leading zeroes allowed
|
||||
EXPECT_EQ(InitVal, val);
|
||||
EXPECT_EQ(nullptr, end);
|
||||
auto benc = Benc;
|
||||
EXPECT_FALSE(tr_bencParseInt(&benc));
|
||||
EXPECT_EQ(std::data(Benc), std::data(benc));
|
||||
}
|
||||
|
||||
TEST_F(VariantTest, str)
|
||||
{
|
||||
auto buf = std::array<uint8_t, 128>{};
|
||||
int err;
|
||||
int n;
|
||||
uint8_t const* end;
|
||||
uint8_t const* str;
|
||||
size_t len;
|
||||
|
||||
// string len is designed to overflow
|
||||
n = tr_snprintf(buf.data(), buf.size(), "%zu:boat", size_t(SIZE_MAX - 2));
|
||||
err = tr_bencParseStr(&buf[0], &buf[n], &end, &str, &len);
|
||||
EXPECT_EQ(EILSEQ, err);
|
||||
EXPECT_EQ(size_t{}, len);
|
||||
EXPECT_EQ(nullptr, str);
|
||||
EXPECT_EQ(nullptr, end);
|
||||
auto benc = "99999999999999999999:boat"sv;
|
||||
auto inout = benc;
|
||||
auto value = tr_bencParseStr(&inout);
|
||||
EXPECT_FALSE(value);
|
||||
EXPECT_EQ(benc, inout);
|
||||
|
||||
// good string
|
||||
n = tr_snprintf(buf.data(), buf.size(), "4:boat");
|
||||
err = tr_bencParseStr(&buf[0], &buf[n], &end, &str, &len);
|
||||
EXPECT_EQ(0, err);
|
||||
EXPECT_EQ(size_t{ 4 }, len);
|
||||
EXPECT_EQ(0, memcmp("boat", str, len));
|
||||
EXPECT_EQ(buf.data() + n, end);
|
||||
str = nullptr;
|
||||
end = nullptr;
|
||||
len = 0;
|
||||
inout = benc = "4:boat";
|
||||
value = tr_bencParseStr(&inout);
|
||||
EXPECT_TRUE(value);
|
||||
EXPECT_EQ("boat"sv, *value);
|
||||
EXPECT_EQ(std::data(benc) + std::size(benc), std::data(inout));
|
||||
|
||||
// string goes past end of buffer
|
||||
err = tr_bencParseStr(&buf[0], &buf[n - 1], &end, &str, &len);
|
||||
EXPECT_EQ(EILSEQ, err);
|
||||
EXPECT_EQ(size_t{}, len);
|
||||
EXPECT_EQ(nullptr, str);
|
||||
EXPECT_EQ(nullptr, end);
|
||||
inout = benc = "4:boa"sv;
|
||||
value = tr_bencParseStr(&inout);
|
||||
EXPECT_FALSE(value);
|
||||
EXPECT_EQ(benc, inout);
|
||||
|
||||
// empty string
|
||||
n = tr_snprintf(buf.data(), buf.size(), "0:");
|
||||
err = tr_bencParseStr(&buf[0], &buf[n], &end, &str, &len);
|
||||
EXPECT_EQ(0, err);
|
||||
EXPECT_EQ(size_t{}, len);
|
||||
EXPECT_EQ('\0', *str);
|
||||
EXPECT_EQ(buf.data() + n, end);
|
||||
str = nullptr;
|
||||
end = nullptr;
|
||||
len = 0;
|
||||
inout = benc = "0:"sv;
|
||||
value = tr_bencParseStr(&inout);
|
||||
EXPECT_TRUE(value);
|
||||
EXPECT_EQ(""sv, *value);
|
||||
EXPECT_EQ(std::data(benc) + std::size(benc), std::data(inout));
|
||||
|
||||
// short string
|
||||
n = tr_snprintf(buf.data(), buf.size(), "3:boat");
|
||||
err = tr_bencParseStr(&buf[0], &buf[n], &end, &str, &len);
|
||||
EXPECT_EQ(0, err);
|
||||
EXPECT_EQ(size_t{ 3 }, len);
|
||||
EXPECT_EQ(0, memcmp("boa", str, len));
|
||||
EXPECT_EQ(buf.data() + 5, end);
|
||||
str = nullptr;
|
||||
end = nullptr;
|
||||
len = 0;
|
||||
inout = benc = "3:boat";
|
||||
value = tr_bencParseStr(&inout);
|
||||
EXPECT_TRUE(value);
|
||||
EXPECT_EQ("boa"sv, *value);
|
||||
EXPECT_EQ(std::data(benc) + benc.find('t'), std::data(inout));
|
||||
}
|
||||
|
||||
TEST_F(VariantTest, parse)
|
||||
|
@ -246,7 +211,7 @@ TEST_F(VariantTest, parse)
|
|||
auto i = int64_t{};
|
||||
auto val = tr_variant{};
|
||||
char const* end;
|
||||
auto err = tr_variantFromBencFull(&val, benc, &end);
|
||||
auto err = tr_variantFromBenc(&val, benc, &end);
|
||||
EXPECT_EQ(0, err);
|
||||
EXPECT_TRUE(tr_variantGetInt(&val, &i));
|
||||
EXPECT_EQ(int64_t(64), i);
|
||||
|
@ -254,7 +219,7 @@ TEST_F(VariantTest, parse)
|
|||
tr_variantFree(&val);
|
||||
|
||||
benc = "li64ei32ei16ee"sv;
|
||||
err = tr_variantFromBencFull(&val, benc, &end);
|
||||
err = tr_variantFromBenc(&val, benc, &end);
|
||||
EXPECT_EQ(0, err);
|
||||
EXPECT_EQ(std::data(benc) + std::size(benc), end);
|
||||
EXPECT_EQ(size_t{ 3 }, tr_variantListSize(&val));
|
||||
|
@ -275,12 +240,12 @@ TEST_F(VariantTest, parse)
|
|||
end = nullptr;
|
||||
|
||||
benc = "lllee"sv;
|
||||
err = tr_variantFromBencFull(&val, benc, &end);
|
||||
err = tr_variantFromBenc(&val, benc, &end);
|
||||
EXPECT_NE(0, err);
|
||||
EXPECT_EQ(nullptr, end);
|
||||
|
||||
benc = "le"sv;
|
||||
err = tr_variantFromBencFull(&val, benc, &end);
|
||||
err = tr_variantFromBenc(&val, benc, &end);
|
||||
EXPECT_EQ(0, err);
|
||||
EXPECT_EQ(std::data(benc) + std::size(benc), end);
|
||||
|
||||
|
@ -315,7 +280,7 @@ TEST_F(VariantTest, bencParseAndReencode)
|
|||
{
|
||||
tr_variant val;
|
||||
char const* end = nullptr;
|
||||
auto const err = tr_variantFromBencFull(&val, test.benc, &end);
|
||||
auto const err = tr_variantFromBenc(&val, test.benc, &end);
|
||||
if (!test.is_good)
|
||||
{
|
||||
EXPECT_NE(0, err);
|
||||
|
@ -340,7 +305,7 @@ TEST_F(VariantTest, bencSortWhenSerializing)
|
|||
|
||||
tr_variant val;
|
||||
char const* end;
|
||||
auto const err = tr_variantFromBencFull(&val, In, &end);
|
||||
auto const err = tr_variantFromBenc(&val, In, &end);
|
||||
EXPECT_EQ(0, err);
|
||||
EXPECT_EQ(std::data(In) + std::size(In), end);
|
||||
|
||||
|
@ -360,7 +325,7 @@ TEST_F(VariantTest, bencMalformedTooManyEndings)
|
|||
|
||||
tr_variant val;
|
||||
char const* end;
|
||||
auto const err = tr_variantFromBencFull(&val, In, &end);
|
||||
auto const err = tr_variantFromBenc(&val, In, &end);
|
||||
EXPECT_EQ(0, err);
|
||||
EXPECT_EQ(std::data(In) + std::size(ExpectedOut), end);
|
||||
|
||||
|
@ -482,7 +447,7 @@ TEST_F(VariantTest, stackSmash)
|
|||
// confirm that it parses
|
||||
char const* end;
|
||||
tr_variant val;
|
||||
auto err = tr_variantFromBencFull(&val, in, &end);
|
||||
auto err = tr_variantFromBenc(&val, in, &end);
|
||||
EXPECT_EQ(0, err);
|
||||
EXPECT_EQ(in.data() + in.size(), end);
|
||||
|
||||
|
|
Loading…
Reference in New Issue