diff --git a/redis/src/storages/redis/impl/sentinel.cpp b/redis/src/storages/redis/impl/sentinel.cpp index 263e0981c5de..8474f29b4c61 100644 --- a/redis/src/storages/redis/impl/sentinel.cpp +++ b/redis/src/storages/redis/impl/sentinel.cpp @@ -355,7 +355,7 @@ void Sentinel::OnPsubscribeReply( return; } const auto& reply_array = reply->data.GetArray(); - if (!reply_array[0].IsString()) { + if (reply_array.empty() || !reply_array[0].IsString()) { return; } if (!strcasecmp(reply_array[0].GetString().c_str(), "PSUBSCRIBE")) { diff --git a/redis/src/storages/redis/impl/sentinel_test.cpp b/redis/src/storages/redis/impl/sentinel_test.cpp index c3d4efaa31a1..de9d1e0bda58 100644 --- a/redis/src/storages/redis/impl/sentinel_test.cpp +++ b/redis/src/storages/redis/impl/sentinel_test.cpp @@ -1,8 +1,15 @@ #include #include +#include +#include +#include +#include + #include +#include + USERVER_NAMESPACE_BEGIN TEST(Sentinel, CreateTmpKey) { @@ -14,4 +21,61 @@ TEST(Sentinel, CreateTmpKey) { } } +TEST(Sentinel, OnPsubscribeReplyEmptyArray) { + using storages::redis::Reply; + using storages::redis::ReplyData; + using storages::redis::impl::Sentinel; + + auto reply = std::make_shared("PSUBSCRIBE", ReplyData{ReplyData::Array{}}); + ASSERT_TRUE(reply->data.IsArray()); + ASSERT_TRUE(reply->data.GetArray().empty()); + + const auto fail_pmessage = [](storages::redis::ServerId, const std::string&, const std::string&, + const std::string&) { FAIL() << "pmessage callback must not fire on empty array"; }; + const auto fail_subscribe = [](storages::redis::ServerId, const std::string&, size_t) { + FAIL() << "subscribe callback must not fire on empty array"; + }; + const auto fail_unsubscribe = [](storages::redis::ServerId, const std::string&, size_t) { + FAIL() << "unsubscribe callback must not fire on empty array"; + }; + + Sentinel::OnPsubscribeReply(fail_pmessage, fail_subscribe, fail_unsubscribe, reply); +} + +TEST(Sentinel, OnPsubscribeReplyTooShortArray) { + using storages::redis::Reply; + using storages::redis::ReplyData; + using storages::redis::impl::Sentinel; + + const auto fail_pmessage = [](storages::redis::ServerId, const std::string&, const std::string&, + const std::string&) { FAIL() << "pmessage callback must not fire on malformed array"; }; + const auto fail_subscribe = [](storages::redis::ServerId, const std::string&, size_t) { + FAIL() << "subscribe callback must not fire on malformed array"; + }; + const auto fail_unsubscribe = [](storages::redis::ServerId, const std::string&, size_t) { + FAIL() << "unsubscribe callback must not fire on malformed array"; + }; + + // A server/proxy may answer with a well-formed array that is missing the + // channel/count fields. Each of these must be ignored, not indexed OOB. + const auto make_array = [](std::vector parts) { + ReplyData::Array array; + for (auto& part : parts) array.emplace_back(std::move(part)); + return array; + }; + + for (auto& parts : std::vector>{ + {"PSUBSCRIBE"}, // command only, no channel/count + {"PSUBSCRIBE", "news.*"}, // missing count + {"PUNSUBSCRIBE"}, // command only + {"PUNSUBSCRIBE", "news.*"}, // missing count + {"PMESSAGE"}, // command only + {"PMESSAGE", "news.*"}, // missing channel/payload + {"PMESSAGE", "news.*", "news.tech"}, // missing payload + }) { + auto reply = std::make_shared("PSUBSCRIBE", ReplyData{make_array(std::move(parts))}); + Sentinel::OnPsubscribeReply(fail_pmessage, fail_subscribe, fail_unsubscribe, reply); + } +} + USERVER_NAMESPACE_END