diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/cgiparam | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/cgiparam')
-rw-r--r-- | library/cpp/cgiparam/cgiparam.cpp | 273 | ||||
-rw-r--r-- | library/cpp/cgiparam/cgiparam.h | 184 | ||||
-rw-r--r-- | library/cpp/cgiparam/cgiparam_ut.cpp | 242 | ||||
-rw-r--r-- | library/cpp/cgiparam/fuzz/main.cpp | 11 | ||||
-rw-r--r-- | library/cpp/cgiparam/fuzz/ya.make | 16 | ||||
-rw-r--r-- | library/cpp/cgiparam/ut/ya.make | 9 | ||||
-rw-r--r-- | library/cpp/cgiparam/ya.make | 16 |
7 files changed, 751 insertions, 0 deletions
diff --git a/library/cpp/cgiparam/cgiparam.cpp b/library/cpp/cgiparam/cgiparam.cpp new file mode 100644 index 0000000000..f3277b8e4b --- /dev/null +++ b/library/cpp/cgiparam/cgiparam.cpp @@ -0,0 +1,273 @@ +#include "cgiparam.h" + +#include <library/cpp/string_utils/scan/scan.h> +#include <library/cpp/string_utils/quote/quote.h> + +#include <util/generic/singleton.h> + +TCgiParameters::TCgiParameters(std::initializer_list<std::pair<TString, TString>> il) { + for (const auto& item : il) { + insert(item); + } +} + +const TString& TCgiParameters::Get(const TStringBuf name, size_t numOfValue) const noexcept { + const auto it = Find(name, numOfValue); + + return end() == it ? Default<TString>() : it->second; +} + +bool TCgiParameters::Erase(const TStringBuf name, size_t pos) { + const auto pair = equal_range(name); + + for (auto it = pair.first; it != pair.second; ++it, --pos) { + if (0 == pos) { + erase(it); + return true; + } + } + + return false; +} + +bool TCgiParameters::Erase(const TStringBuf name, const TStringBuf val) { + const auto pair = equal_range(name); + + bool found = false; + for (auto it = pair.first; it != pair.second;) { + if (val == it->second) { + it = erase(it); + found = true; + } else { + ++it; + } + } + + return found; +} + +size_t TCgiParameters::EraseAll(const TStringBuf name) { + size_t num = 0; + + const auto pair = equal_range(name); + + for (auto it = pair.first; it != pair.second; erase(it++), ++num) + ; + + return num; +} + +void TCgiParameters::JoinUnescaped(const TStringBuf key, char sep, TStringBuf val) { + const auto pair = equal_range(key); + auto it = pair.first; + + if (it == pair.second) { // not found + if (val.IsInited()) { + emplace_hint(it, TString(key), TString(val)); + } + } else { + TString& dst = it->second; + + for (++it; it != pair.second; erase(it++)) { + dst += sep; + dst.AppendNoAlias(it->second.data(), it->second.size()); + } + + if (val.IsInited()) { + dst += sep; + dst += val; + } + } +} + +static inline TString DoUnescape(const TStringBuf s) { + TString res; + + res.reserve(CgiUnescapeBufLen(s.size())); + res.ReserveAndResize(CgiUnescape(res.begin(), s).size()); + + return res; +} + +void TCgiParameters::InsertEscaped(const TStringBuf name, const TStringBuf value) { + InsertUnescaped(DoUnescape(name), DoUnescape(value)); +} + +template <bool addAll, class F> +static inline void DoScan(const TStringBuf s, F& f) { + ScanKeyValue<addAll, '&', '='>(s, f); +} + +struct TAddEscaped { + TCgiParameters* C; + + inline void operator()(const TStringBuf key, const TStringBuf val) { + C->InsertEscaped(key, val); + } +}; + +void TCgiParameters::Scan(const TStringBuf query, bool form) { + Flush(); + form ? ScanAdd(query) : ScanAddAll(query); +} + +void TCgiParameters::ScanAdd(const TStringBuf query) { + TAddEscaped f = {this}; + + DoScan<false>(query, f); +} + +void TCgiParameters::ScanAddUnescaped(const TStringBuf query) { + auto f = [this](const TStringBuf key, const TStringBuf val) { + this->InsertUnescaped(key, val); + }; + + DoScan<false>(query, f); +} + +void TCgiParameters::ScanAddAllUnescaped(const TStringBuf query) { + auto f = [this](const TStringBuf key, const TStringBuf val) { + this->InsertUnescaped(key, val); + }; + + DoScan<true>(query, f); +} + +void TCgiParameters::ScanAddAll(const TStringBuf query) { + TAddEscaped f = {this}; + + DoScan<true>(query, f); +} + +TString TCgiParameters::Print() const { + TString res; + + res.reserve(PrintSize()); + const char* end = Print(res.begin()); + res.ReserveAndResize(end - res.data()); + + return res; +} + +char* TCgiParameters::Print(char* res) const { + if (empty()) { + return res; + } + + for (auto i = begin();;) { + res = CGIEscape(res, i->first); + *res++ = '='; + res = CGIEscape(res, i->second); + + if (++i == end()) { + break; + } + + *res++ = '&'; + } + + return res; +} + +size_t TCgiParameters::PrintSize() const noexcept { + size_t res = size(); // for '&' + + for (const auto& i : *this) { + res += CgiEscapeBufLen(i.first.size() + i.second.size()); // extra zero will be used for '=' + } + + return res; +} + +TString TCgiParameters::QuotedPrint(const char* safe) const { + if (empty()) { + return TString(); + } + + TString res; + res.ReserveAndResize(PrintSize()); + + char* ptr = res.begin(); + for (auto i = begin();;) { + ptr = Quote(ptr, i->first, safe); + *ptr++ = '='; + ptr = Quote(ptr, i->second, safe); + + if (++i == end()) { + break; + } + + *ptr++ = '&'; + } + + res.ReserveAndResize(ptr - res.data()); + return res; +} + +TCgiParameters::const_iterator TCgiParameters::Find(const TStringBuf name, size_t pos) const noexcept { + const auto pair = equal_range(name); + + for (auto it = pair.first; it != pair.second; ++it, --pos) { + if (0 == pos) { + return it; + } + } + + return end(); +} + +bool TCgiParameters::Has(const TStringBuf name, const TStringBuf value) const noexcept { + const auto pair = equal_range(name); + + for (auto it = pair.first; it != pair.second; ++it) { + if (value == it->second) { + return true; + } + } + + return false; +} + +TQuickCgiParam::TQuickCgiParam(const TStringBuf cgiParamStr) { + UnescapeBuf.reserve(CgiUnescapeBufLen(cgiParamStr.size())); + char* buf = UnescapeBuf.begin(); + + auto f = [this, &buf](const TStringBuf key, const TStringBuf val) { + TStringBuf name = CgiUnescapeBuf(buf, key); + buf += name.size() + 1; + TStringBuf value = CgiUnescapeBuf(buf, val); + buf += value.size() + 1; + Y_ASSERT(buf <= UnescapeBuf.begin() + UnescapeBuf.capacity() + 1 /*trailing zero*/); + emplace(name, value); + }; + + DoScan<false>(cgiParamStr, f); + + if (buf != UnescapeBuf.begin()) { + UnescapeBuf.ReserveAndResize(buf - UnescapeBuf.begin() - 1 /*trailing zero*/); + } +} + +const TStringBuf& TQuickCgiParam::Get(const TStringBuf name, size_t pos) const noexcept { + const auto pair = equal_range(name); + + for (auto it = pair.first; it != pair.second; ++it, --pos) { + if (0 == pos) { + return it->second; + } + } + + return Default<TStringBuf>(); +} + +bool TQuickCgiParam::Has(const TStringBuf name, const TStringBuf value) const noexcept { + const auto pair = equal_range(name); + + for (auto it = pair.first; it != pair.second; ++it) { + if (value == it->second) { + return true; + } + } + + return false; +} diff --git a/library/cpp/cgiparam/cgiparam.h b/library/cpp/cgiparam/cgiparam.h new file mode 100644 index 0000000000..87d1ab0ad4 --- /dev/null +++ b/library/cpp/cgiparam/cgiparam.h @@ -0,0 +1,184 @@ +#pragma once + +#include <library/cpp/iterator/iterate_values.h> + +#include <util/generic/iterator_range.h> +#include <util/generic/map.h> +#include <util/generic/strbuf.h> +#include <util/generic/string.h> + +#include <initializer_list> + +struct TStringLess { + template <class T1, class T2> + inline bool operator()(const T1& t1, const T2& t2) const noexcept { + return TStringBuf(t1) < TStringBuf(t2); + } +}; + +class TCgiParameters: public TMultiMap<TString, TString> { +public: + TCgiParameters() = default; + + explicit TCgiParameters(const TStringBuf cgiParamStr) { + Scan(cgiParamStr); + } + + TCgiParameters(std::initializer_list<std::pair<TString, TString>> il); + + void Flush() { + erase(begin(), end()); + } + + size_t EraseAll(const TStringBuf name); + + size_t NumOfValues(const TStringBuf name) const noexcept { + return count(name); + } + + TString operator()() const { + return Print(); + } + + void Scan(const TStringBuf cgiParStr, bool form = true); + void ScanAdd(const TStringBuf cgiParStr); + void ScanAddUnescaped(const TStringBuf cgiParStr); + void ScanAddAllUnescaped(const TStringBuf cgiParStr); + void ScanAddAll(const TStringBuf cgiParStr); + + /// Returns the string representation of all the stored parameters + /** + * @note The returned string has format <name1>=<value1>&<name2>=<value2>&... + * @note Names and values in the returned string are CGI-escaped. + */ + TString Print() const; + char* Print(char* res) const; + + Y_PURE_FUNCTION + size_t PrintSize() const noexcept; + + /** The same as Print* except that RFC-3986 reserved characters are escaped. + * @param safe - set of characters to be skipped in escaping + */ + TString QuotedPrint(const char* safe = "/") const; + + Y_PURE_FUNCTION + auto Range(const TStringBuf name) const noexcept { + return IterateValues(MakeIteratorRange(equal_range(name))); + } + + Y_PURE_FUNCTION + const_iterator Find(const TStringBuf name, size_t numOfValue = 0) const noexcept; + + Y_PURE_FUNCTION + bool Has(const TStringBuf name, const TStringBuf value) const noexcept; + + Y_PURE_FUNCTION + bool Has(const TStringBuf name) const noexcept { + const auto pair = equal_range(name); + return pair.first != pair.second; + } + /// Returns value by name + /** + * @note The returned value is CGI-unescaped. + */ + Y_PURE_FUNCTION + const TString& Get(const TStringBuf name, size_t numOfValue = 0) const noexcept; + + void InsertEscaped(const TStringBuf name, const TStringBuf value); + +#if !defined(__GLIBCXX__) + template <typename TName, typename TValue> + inline void InsertUnescaped(TName&& name, TValue&& value) { + // TStringBuf use as TName or TValue is C++17 actually. + // There is no pair constructor available in C++14 when required type + // is not implicitly constructible from given type. + // But libc++ pair allows this with C++14. + emplace(std::forward<TName>(name), std::forward<TValue>(value)); + } +#else + template <typename TName, typename TValue> + inline void InsertUnescaped(TName&& name, TValue&& value) { + emplace(TString(name), TString(value)); + } +#endif + + // replace all values for a given key with new values + template <typename TIter> + void ReplaceUnescaped(const TStringBuf key, TIter valuesBegin, const TIter valuesEnd); + + void ReplaceUnescaped(const TStringBuf key, std::initializer_list<TStringBuf> values) { + ReplaceUnescaped(key, values.begin(), values.end()); + } + + void ReplaceUnescaped(const TStringBuf key, const TStringBuf value) { + ReplaceUnescaped(key, {value}); + } + + // join multiple values into a single one using a separator + // if val is a [possibly empty] non-NULL string, append it as well + void JoinUnescaped(const TStringBuf key, char sep, TStringBuf val = TStringBuf()); + + bool Erase(const TStringBuf name, size_t numOfValue = 0); + bool Erase(const TStringBuf name, const TStringBuf val); + + inline const char* FormField(const TStringBuf name, size_t numOfValue = 0) const { + const_iterator it = Find(name, numOfValue); + + if (it == end()) { + return nullptr; + } + + return it->second.data(); + } +}; + +template <typename TIter> +void TCgiParameters::ReplaceUnescaped(const TStringBuf key, TIter valuesBegin, const TIter valuesEnd) { + const auto oldRange = equal_range(key); + auto current = oldRange.first; + + // reuse as many existing nodes as possible (probably none) + for (; valuesBegin != valuesEnd && current != oldRange.second; ++valuesBegin, ++current) { + current->second = *valuesBegin; + } + + // if there were more nodes than we need to insert then erase remaining ones + for (; current != oldRange.second; erase(current++)) { + } + + // if there were less nodes than we need to insert then emplace the rest of the range + if (valuesBegin != valuesEnd) { + const TString keyStr = TString(key); + for (; valuesBegin != valuesEnd; ++valuesBegin) { + emplace_hint(oldRange.second, keyStr, TString(*valuesBegin)); + } + } +} + +/** TQuickCgiParam is a faster non-editable version of TCgiParameters. + * Care should be taken when replacing: + * - note that the result of Get() is invalidated when TQuickCgiParam object is destroyed. + */ + +class TQuickCgiParam: public TMultiMap<TStringBuf, TStringBuf> { +public: + TQuickCgiParam() = default; + + explicit TQuickCgiParam(const TStringBuf cgiParamStr); + + Y_PURE_FUNCTION + bool Has(const TStringBuf name, const TStringBuf value) const noexcept; + + Y_PURE_FUNCTION + bool Has(const TStringBuf name) const noexcept { + const auto pair = equal_range(name); + return pair.first != pair.second; + } + + Y_PURE_FUNCTION + const TStringBuf& Get(const TStringBuf name, size_t numOfValue = 0) const noexcept; + +private: + TString UnescapeBuf; +}; diff --git a/library/cpp/cgiparam/cgiparam_ut.cpp b/library/cpp/cgiparam/cgiparam_ut.cpp new file mode 100644 index 0000000000..a562342084 --- /dev/null +++ b/library/cpp/cgiparam/cgiparam_ut.cpp @@ -0,0 +1,242 @@ +#include "cgiparam.h" + +#include <library/cpp/testing/unittest/registar.h> + +Y_UNIT_TEST_SUITE(TCgiParametersTest) { + Y_UNIT_TEST(TestScan1) { + TCgiParameters C; + C.Scan("aaa=b%62b&ccc=ddd&ag0="); + UNIT_ASSERT_EQUAL(C.Get("aaa") == "bbb", true); + UNIT_ASSERT_EQUAL(C.NumOfValues("ag0") == 1, true); + UNIT_ASSERT(C.Has("ccc", "ddd")); + UNIT_ASSERT(C.Has("ag0", "")); + UNIT_ASSERT(!C.Has("a", "bbb")); + UNIT_ASSERT(!C.Has("aaa", "bb")); + + UNIT_ASSERT(C.Has("ccc")); + UNIT_ASSERT(!C.Has("zzzzzz")); + } + + Y_UNIT_TEST(TestQuick) { + TQuickCgiParam C("aaa=b%62b&ccc=ddd&ag0="); + UNIT_ASSERT_EQUAL(C.Get("aaa") == "bbb", true); + UNIT_ASSERT(C.Has("ccc", "ddd")); + UNIT_ASSERT(C.Has("ag0", "")); + UNIT_ASSERT(!C.Has("a", "bbb")); + UNIT_ASSERT(!C.Has("aaa", "bb")); + + UNIT_ASSERT(C.Has("ccc")); + UNIT_ASSERT(!C.Has("zzzzzz")); + + TQuickCgiParam D = std::move(C); + UNIT_ASSERT(D.Has("aaa")); + + TQuickCgiParam E(""); + UNIT_ASSERT(!E.Has("aaa")); + + C = std::move(E); + UNIT_ASSERT(!C.Has("aaa")); + } + + Y_UNIT_TEST(TestScan2) { + const TString parsee("=000&aaa=bbb&ag0=&ccc=ddd"); + TCgiParameters c; + c.Scan(parsee); + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), parsee); + } + + Y_UNIT_TEST(TestScan3) { + const TString parsee("aaa=bbb&ag0=&ccc=ddd"); + TCgiParameters c; + c.Scan(parsee); + + c.InsertUnescaped("d", "xxx"); + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), parsee + "&d=xxx"); + } + + Y_UNIT_TEST(TestScanAddAll1) { + TCgiParameters c; + c.ScanAddAll("qw"); + + UNIT_ASSERT_VALUES_EQUAL(c.size(), 1u); + UNIT_ASSERT(c.Get("qw").empty()); + } + + Y_UNIT_TEST(TestScanAddAll2) { + TCgiParameters c; + c.ScanAddAll("qw&"); + + UNIT_ASSERT_VALUES_EQUAL(c.size(), 1u); + UNIT_ASSERT(c.Get("qw").empty()); + } + + Y_UNIT_TEST(TestScanAddAll3) { + TCgiParameters c; + c.ScanAddAll("qw=1&x"); + + UNIT_ASSERT_VALUES_EQUAL(c.size(), 2u); + UNIT_ASSERT_VALUES_EQUAL(c.Get("qw"), "1"); + UNIT_ASSERT(c.Get("x").empty()); + } + + Y_UNIT_TEST(TestScanAddAll4) { + TCgiParameters c; + c.ScanAddAll("ccc=1&aaa=1&ccc=3&bbb&ccc=2"); + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "aaa=1&bbb=&ccc=1&ccc=3&ccc=2"); + } + + Y_UNIT_TEST(TestScanAddAllUnescaped1) { + TCgiParameters c; + c.ScanAddAllUnescaped("ccc=1&aaa=1&ccc=3&bbb&ccc=2"); + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "aaa=1&bbb=&ccc=1&ccc=3&ccc=2"); + } + + Y_UNIT_TEST(TestScanAddAllUnescaped2) { + TCgiParameters c; + c.ScanAddAllUnescaped("text=something&null"); + + UNIT_ASSERT_VALUES_EQUAL(c.size(), 2u); + UNIT_ASSERT_VALUES_EQUAL(c.Get("text"), "something"); + UNIT_ASSERT(c.Get("null").empty()); + } + + Y_UNIT_TEST(TestScanAddAllUnescaped3) { + TCgiParameters c; + c.ScanAddAllUnescaped("text=%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%2C"); + + UNIT_ASSERT_VALUES_EQUAL(c.Get("text"), "%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%2C"); + } + + Y_UNIT_TEST(TestEraseAll) { + TCgiParameters c; + c.ScanAddAll("par=1&aaa=1&par=2&bbb&par=3"); + c.EraseAll("par"); + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "aaa=1&bbb="); + } + + Y_UNIT_TEST(TestErase) { + TCgiParameters c; + c.ScanAddAll("par=1&aaa=1&par=2&bbb&par=3&par=1"); + + c.Erase("par", 1); + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "aaa=1&bbb=&par=1&par=3&par=1"); + + c.Erase("par", "1"); + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "aaa=1&bbb=&par=3"); + } + + Y_UNIT_TEST(TestReplaceUnescaped1) { + TCgiParameters c; + c.ScanAddAll("many_keys=1&aaa=1&many_keys=2&bbb&many_keys=3"); + c.ReplaceUnescaped("many_keys", "new_value"); + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "aaa=1&bbb=&many_keys=new_value"); + } + + Y_UNIT_TEST(TestReplaceUnescaped2) { + TCgiParameters c; + c.ScanAddAll("par=1&only_one=1&par=2&bbb&par=3"); + c.ReplaceUnescaped("only_one", "new_value"); + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "bbb=&only_one=new_value&par=1&par=2&par=3"); + } + + Y_UNIT_TEST(TestReplaceUnescaped3) { + TCgiParameters c; + c.ScanAddAll("par=1&aaa=1&par=2&bbb&par=3"); + c.ReplaceUnescaped("no_such_key", "new_value"); + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "aaa=1&bbb=&no_such_key=new_value&par=1&par=2&par=3"); + } + + Y_UNIT_TEST(TestReplaceUnescapedRange1) { + TCgiParameters c; + c.ScanAddAll("par=1&aaa=1&par=2&bbb&par=3"); + c.ReplaceUnescaped("par", {"x", "y", "z"}); // 3 old values, 3 new values + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "aaa=1&bbb=&par=x&par=y&par=z"); + } + + Y_UNIT_TEST(TestReplaceUnescapedRange2) { + TCgiParameters c; + c.ScanAddAll("par=1&aaa=1&par=2&bbb"); + c.ReplaceUnescaped("par", {"x", "y", "z"}); // 2 old values, 3 new values + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "aaa=1&bbb=&par=x&par=y&par=z"); + } + + Y_UNIT_TEST(TestReplaceUnescapedRange3) { + TCgiParameters c; + c.ScanAddAll("par=1&aaa=1&par=2&bbb&par=3"); + c.ReplaceUnescaped("par", {"x", "y"}); // 3 old values, 2 new values + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "aaa=1&bbb=&par=x&par=y"); + } + + Y_UNIT_TEST(TestNumOfValues) { + TCgiParameters c; + c.ScanAddAll("par=1&aaa=1&par=2&bbb&par=3"); + + UNIT_ASSERT_VALUES_EQUAL(c.NumOfValues("par"), 3u); + } + + Y_UNIT_TEST(TestUnscape) { + TCgiParameters c("f=1&t=%84R%84%7C%84%80%84%7E&reqenc=SHIFT_JIS&p=0"); + UNIT_ASSERT_VALUES_EQUAL(c.Get("t"), "\x84R\x84\x7C\x84\x80\x84\x7E"); + } + + Y_UNIT_TEST(TestEmpty) { + UNIT_ASSERT(TCgiParameters().Print().empty()); + } + + Y_UNIT_TEST(TestJoinUnescaped) { + TCgiParameters c; + + c.Scan("foo=1&foo=2"); + c.JoinUnescaped("foo", ';', "0"); + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "foo=1;2;0"); + } + + Y_UNIT_TEST(TestContInit) { + TCgiParameters c = {std::make_pair("a", "a1"), std::make_pair("b", "b1"), std::make_pair("a", "a2")}; + + UNIT_ASSERT_VALUES_EQUAL(c.NumOfValues("a"), 2u); + UNIT_ASSERT_VALUES_EQUAL(c.NumOfValues("b"), 1u); + + UNIT_ASSERT_VALUES_EQUAL(c.Get("b"), "b1"); + UNIT_ASSERT_VALUES_EQUAL(c.Get("a", 0), "a1"); + UNIT_ASSERT_VALUES_EQUAL(c.Get("a", 1), "a2"); + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "a=a1&a=a2&b=b1"); + } + + Y_UNIT_TEST(TestPrintAsQuote) { + TCgiParameters c = { + std::make_pair("aaa", "value/with/slashes"), + std::make_pair("b/b/b", "value_without_slashes"), + std::make_pair("ccc", "value")}; + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "aaa=value/with/slashes&b/b/b=value_without_slashes&ccc=value"); + UNIT_ASSERT_VALUES_EQUAL(c.QuotedPrint(""), "aaa=value%2Fwith%2Fslashes&b%2Fb%2Fb=value_without_slashes&ccc=value"); + } + + Y_UNIT_TEST(TestPrintAsQuoteEmpty) { + TCgiParameters c = {}; + UNIT_ASSERT_VALUES_EQUAL(c.QuotedPrint(""), ""); + } + + Y_UNIT_TEST(TestPrintAsQuoteEmptyKeyOrValue) { + TCgiParameters c = { + std::make_pair("", "value/of/empty"), + std::make_pair("key/for/empty", "")}; + + UNIT_ASSERT_VALUES_EQUAL(c.Print(), "=value/of/empty&key/for/empty="); + UNIT_ASSERT_VALUES_EQUAL(c.QuotedPrint(""), "=value%2Fof%2Fempty&key%2Ffor%2Fempty="); + } +} diff --git a/library/cpp/cgiparam/fuzz/main.cpp b/library/cpp/cgiparam/fuzz/main.cpp new file mode 100644 index 0000000000..69d82b5f32 --- /dev/null +++ b/library/cpp/cgiparam/fuzz/main.cpp @@ -0,0 +1,11 @@ +#include <library/cpp/cgiparam/cgiparam.h> + +extern "C" int LLVMFuzzerTestOneInput(const ui8* data, size_t size) { + try { + TCgiParameters(TStringBuf((const char*)data, size)); + } catch (...) { + // ¯\_(ツ)_/¯ + } + + return 0; // Non-zero return values are reserved for future use. +} diff --git a/library/cpp/cgiparam/fuzz/ya.make b/library/cpp/cgiparam/fuzz/ya.make new file mode 100644 index 0000000000..8fb9d50d3b --- /dev/null +++ b/library/cpp/cgiparam/fuzz/ya.make @@ -0,0 +1,16 @@ +FUZZ() + +OWNER( + pg + g:util +) + +SRCS( + main.cpp +) + +PEERDIR( + library/cpp/cgiparam +) + +END() diff --git a/library/cpp/cgiparam/ut/ya.make b/library/cpp/cgiparam/ut/ya.make new file mode 100644 index 0000000000..1eee403951 --- /dev/null +++ b/library/cpp/cgiparam/ut/ya.make @@ -0,0 +1,9 @@ +UNITTEST_FOR(library/cpp/cgiparam) + +OWNER(g:util) + +SRCS( + cgiparam_ut.cpp +) + +END() diff --git a/library/cpp/cgiparam/ya.make b/library/cpp/cgiparam/ya.make new file mode 100644 index 0000000000..fa1a6a13c0 --- /dev/null +++ b/library/cpp/cgiparam/ya.make @@ -0,0 +1,16 @@ +LIBRARY() + +OWNER(g:util) + +SRCS( + cgiparam.cpp + cgiparam.h +) + +PEERDIR( + library/cpp/iterator + library/cpp/string_utils/quote + library/cpp/string_utils/scan +) + +END() |