Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/tgbot/TgTypeParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,10 @@ class TGBOT_API TgTypeParser {
}

void appendToJson(std::string& json, const std::string& varName, const std::string& value) const;

/// Appends `"varName":["escapedValue1","escapedValue2",...],` to `json`.
/// Does nothing when `values` is empty. Each element is JSON-escaped.
void appendStringArrayToJson(std::string& json, const std::string& varName, const std::vector<std::string>& values) const;
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/Api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ bool Api::setWebhook(const std::string& url,
if (allowedUpdates != nullptr) {
auto allowedUpdatesJson = _tgTypeParser.parseArray<std::string>(
[] (const std::string& s)->std::string {
return s;
return "\"" + StringTools::escapeJsonString(s) + "\"";
}, *allowedUpdates);
args.emplace_back("allowed_updates", allowedUpdatesJson);
}
Expand Down
55 changes: 27 additions & 28 deletions src/TgTypeParser.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "tgbot/TgTypeParser.h"

#include "tgbot/tools/StringTools.h"

namespace TgBot {

Update::Ptr TgTypeParser::parseJsonAndGetUpdate(const boost::property_tree::ptree& data) const {
Expand Down Expand Up @@ -97,10 +99,7 @@ std::string TgTypeParser::parseWebhookInfo(const WebhookInfo::Ptr& object) const
appendToJson(result, "last_error_message", object->lastErrorMessage);
appendToJson(result, "last_synchronization_error_date", object->lastSynchronizationErrorDate);
appendToJson(result, "max_connections", object->maxConnections);
appendToJson(result, "allowed_updates", parseArray<std::string>(
[] (const std::string& s)->std::string {
return s;
}, object->allowedUpdates));
appendStringArrayToJson(result, "allowed_updates", object->allowedUpdates);
removeLastComma(result);
result += '}';
return result;
Expand Down Expand Up @@ -227,10 +226,7 @@ std::string TgTypeParser::parseChat(const Chat::Ptr& object) const {
appendToJson(result, "last_name", object->lastName);
appendToJson(result, "is_forum", object->isForum);
appendToJson(result, "photo", parseChatPhoto(object->photo));
appendToJson(result, "active_usernames", parseArray<std::string>(
[] (const std::string& s)->std::string {
return s;
}, object->activeUsernames));
appendStringArrayToJson(result, "active_usernames", object->activeUsernames);
appendToJson(result, "birthdate", parseBirthdate(object->birthdate));
appendToJson(result, "business_intro", parseBusinessIntro(object->businessIntro));
appendToJson(result, "business_location", parseBusinessLocation(object->businessLocation));
Expand Down Expand Up @@ -1650,10 +1646,7 @@ std::string TgTypeParser::parseGiveaway(const Giveaway::Ptr& object) const {
appendToJson(result, "only_new_members", object->onlyNewMembers);
appendToJson(result, "has_public_winners", object->hasPublicWinners);
appendToJson(result, "prize_description", object->prizeDescription);
appendToJson(result, "country_codes", parseArray<std::string>(
[] (const std::string& s)->std::string {
return s;
}, object->countryCodes));
appendStringArrayToJson(result, "country_codes", object->countryCodes);
appendToJson(result, "premium_subscription_month_count", object->premiumSubscriptionMonthCount);
removeLastComma(result);
result += '}';
Expand Down Expand Up @@ -3819,15 +3812,9 @@ std::string TgTypeParser::parseInputSticker(const InputSticker::Ptr& object) con
result += '{';
appendToJson(result, "sticker", object->sticker);
appendToJson(result, "format", object->format);
appendToJson(result, "emoji_list", parseArray<std::string>(
[] (const std::string& s)->std::string {
return s;
}, object->emojiList));
appendStringArrayToJson(result, "emoji_list", object->emojiList);
appendToJson(result, "mask_position", parseMaskPosition(object->maskPosition));
appendToJson(result, "keywords", parseArray<std::string>(
[] (const std::string& s)->std::string {
return s;
}, object->keywords));
appendStringArrayToJson(result, "keywords", object->keywords);
removeLastComma(result);
result += '}';
return result;
Expand Down Expand Up @@ -5416,10 +5403,7 @@ std::string TgTypeParser::parsePassportElementErrorFiles(const PassportElementEr
// This function will be called by parsePassportElementError(), so I don't add
// curly brackets to the result std::string.
std::string result;
appendToJson(result, "file_hashes",
parseArray<std::string>([] (const std::string& s)->std::string {
return s;
}, object->fileHashes));
appendStringArrayToJson(result, "file_hashes", object->fileHashes);
// The last comma will be erased by parsePassportElementError().
return result;
}
Expand Down Expand Up @@ -5460,10 +5444,7 @@ std::string TgTypeParser::parsePassportElementErrorTranslationFiles(const Passpo
// This function will be called by parsePassportElementError(), so I don't add
// curly brackets to the result std::string.
std::string result;
appendToJson(result, "file_hashes",
parseArray<std::string>([] (const std::string& s)->std::string {
return s;
}, object->fileHashes));
appendStringArrayToJson(result, "file_hashes", object->fileHashes);
// The last comma will be erased by parsePassportElementError().
return result;
}
Expand Down Expand Up @@ -5582,6 +5563,24 @@ std::string TgTypeParser::parseGenericReply(const GenericReply::Ptr& object) con
return "";
}

void TgTypeParser::appendStringArrayToJson(std::string& json, const std::string& varName, const std::vector<std::string>& values) const {
if (values.empty()) {
return;
}
json += '"';
json += varName;
json += R"(":[)";
for (std::size_t i = 0; i < values.size(); ++i) {
if (i != 0) {
json += ',';
}
json += '"';
json += StringTools::escapeJsonString(values[i]);
json += '"';
}
json += "],";
}

void TgTypeParser::appendToJson(std::string& json, const std::string& varName, const std::string& value) const {
if (value.empty()) {
return;
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
set(TEST_SRC_LIST
main.cpp
tgbot/Api.cpp
tgbot/TgTypeParser.cpp
tgbot/net/Url.cpp
tgbot/net/HttpParser.cpp
tgbot/tools/StringTools.cpp
Expand Down
61 changes: 61 additions & 0 deletions test/tgbot/TgTypeParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include <memory>
#include <string>
#include <vector>

#include <boost/test/unit_test.hpp>

#include "tgbot/TgTypeParser.h"
#include "tgbot/types/InputSticker.h"

using namespace std;
using namespace TgBot;

BOOST_AUTO_TEST_SUITE(tTgTypeParser)

// Regression tests for https://github.com/reo7sp/tgbot-cpp/issues/346 —
// string arrays were emitted as `[a,b]` (unquoted identifiers) instead of
// `["a","b"]`, producing invalid JSON and breaking sticker creation.

BOOST_AUTO_TEST_CASE(parseInputStickerEmojiListIsJsonArray) {
TgTypeParser parser;
auto sticker = make_shared<InputSticker>();
sticker->sticker = "file_id_abc";
sticker->format = "static";
sticker->emojiList = {"smile", "heart"};

const string json = parser.parseInputSticker(sticker);

BOOST_CHECK(json.find(R"("emoji_list":["smile","heart"])") != string::npos);
// The pre-fix output emitted the array as `[smile,heart]` — assert no regression.
BOOST_CHECK(json.find("[smile,heart]") == string::npos);
}

BOOST_AUTO_TEST_CASE(parseInputStickerEscapesJsonSpecialsInEmoji) {
TgTypeParser parser;
auto sticker = make_shared<InputSticker>();
sticker->sticker = "id";
sticker->format = "static";
sticker->emojiList = {R"(a"b)", R"(c\d)"};

const string json = parser.parseInputSticker(sticker);

BOOST_CHECK(json.find(R"("a\"b")") != string::npos);
BOOST_CHECK(json.find(R"("c\\d")") != string::npos);
}

BOOST_AUTO_TEST_CASE(parseInputStickerOmitsEmptyKeywords) {
// Empty arrays should not appear in the output at all, matching the
// behaviour of the previous `appendToJson(... parseArray(...))` pattern.
TgTypeParser parser;
auto sticker = make_shared<InputSticker>();
sticker->sticker = "id";
sticker->format = "static";
sticker->emojiList = {"a"};
// keywords is default-constructed empty

const string json = parser.parseInputSticker(sticker);

BOOST_CHECK(json.find("keywords") == string::npos);
}

BOOST_AUTO_TEST_SUITE_END()
Loading