aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/cgiparam
diff options
context:
space:
mode:
authorDevtools Arcadia <arcadia-devtools@yandex-team.ru>2022-02-07 18:08:42 +0300
committerDevtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net>2022-02-07 18:08:42 +0300
commit1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch)
treee26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/cgiparam
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/cgiparam')
-rw-r--r--library/cpp/cgiparam/cgiparam.cpp273
-rw-r--r--library/cpp/cgiparam/cgiparam.h184
-rw-r--r--library/cpp/cgiparam/cgiparam_ut.cpp242
-rw-r--r--library/cpp/cgiparam/fuzz/main.cpp11
-rw-r--r--library/cpp/cgiparam/fuzz/ya.make16
-rw-r--r--library/cpp/cgiparam/ut/ya.make9
-rw-r--r--library/cpp/cgiparam/ya.make16
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()