aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2018-09-06 19:55:48 -0400
committerJack Lloyd <[email protected]>2018-09-09 11:37:25 -0400
commitfb656ae863fd5b755e4bc0e779b95d34e1106219 (patch)
tree8b53a3a43ebe836c5b4c63b78e7d56c9f16b8c0e
parent992d2803181b34415c25e013a40ab935eb71a9e3 (diff)
Add read_kv for parsing strings with key/value pairs
This is a contribution by Ribose Inc (@riboseinc)
-rw-r--r--src/lib/utils/parsing.h29
-rw-r--r--src/lib/utils/read_kv.cpp85
-rw-r--r--src/tests/data/utils/read_kv.vec50
-rw-r--r--src/tests/test_utils.cpp77
4 files changed, 238 insertions, 3 deletions
diff --git a/src/lib/utils/parsing.h b/src/lib/utils/parsing.h
index 9185cfaad..12cb3fa34 100644
--- a/src/lib/utils/parsing.h
+++ b/src/lib/utils/parsing.h
@@ -5,8 +5,8 @@
* Botan is released under the Simplified BSD License (see license.txt)
*/
-#ifndef BOTAN_PARSER_H_
-#define BOTAN_PARSER_H_
+#ifndef BOTAN_PARSING_UTILS_H_
+#define BOTAN_PARSING_UTILS_H_
#include <botan/types.h>
#include <string>
@@ -40,6 +40,8 @@ BOTAN_PUBLIC_API(2,0) std::vector<std::string> split_on(
* Split a string on a character predicate
* @param str the input string
* @param pred the predicate
+*
+* This function will likely be removed in a future release
*/
BOTAN_PUBLIC_API(2,0) std::vector<std::string>
split_on_pred(const std::string& str,
@@ -143,9 +145,30 @@ BOTAN_PUBLIC_API(2,0) std::string ipv4_to_string(uint32_t ip_addr);
std::map<std::string, std::string> BOTAN_PUBLIC_API(2,0) read_cfg(std::istream& is);
+/**
+* Accepts key value pairs deliminated by commas:
+*
+* "" (returns empty map)
+* "K=V" (returns map {'K': 'V'})
+* "K1=V1,K2=V2"
+* "K1=V1,K2=V2,K3=V3"
+* "K1=V1,K2=V2,K3=a_value\,with\,commas_and_\=equals"
+*
+* Values may be empty, keys must be non-empty and unique. Duplicate
+* keys cause an exception.
+*
+* Within both key and value, comma and equals can be escaped with
+* backslash. Backslash can also be escaped.
+*/
+std::map<std::string, std::string> BOTAN_PUBLIC_API(2,8) read_kv(const std::string& kv);
+
std::string BOTAN_PUBLIC_API(2,0) clean_ws(const std::string& s);
-bool BOTAN_PUBLIC_API(2,0) host_wildcard_match(const std::string& wildcard, const std::string& host);
+/**
+* Check if the given hostname is a match for the specified wildcard
+*/
+bool BOTAN_PUBLIC_API(2,0) host_wildcard_match(const std::string& wildcard,
+ const std::string& host);
}
diff --git a/src/lib/utils/read_kv.cpp b/src/lib/utils/read_kv.cpp
new file mode 100644
index 000000000..cdc84c622
--- /dev/null
+++ b/src/lib/utils/read_kv.cpp
@@ -0,0 +1,85 @@
+/*
+* (C) 2018 Ribose Inc
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include <botan/parsing.h>
+#include <botan/exceptn.h>
+
+namespace Botan {
+
+std::map<std::string, std::string> read_kv(const std::string& kv)
+ {
+ std::map<std::string, std::string> m;
+ if(kv == "")
+ return m;
+
+ std::vector<std::string> parts;
+
+ try
+ {
+ parts = split_on(kv, ',');
+ }
+ catch(std::exception&)
+ {
+ throw Invalid_Argument("Bad KV spec");
+ }
+
+ bool escaped = false;
+ bool reading_key = true;
+ std::string cur_key;
+ std::string cur_val;
+
+ for(char c : kv)
+ {
+ if(c == '\\' && !escaped)
+ {
+ escaped = true;
+ }
+ else if(c == ',' && !escaped)
+ {
+ if(cur_key.empty())
+ throw Invalid_Argument("Bad KV spec empty key");
+
+ if(m.find(cur_key) != m.end())
+ throw Invalid_Argument("Bad KV spec duplicated key");
+ m[cur_key] = cur_val;
+ cur_key = "";
+ cur_val = "";
+ reading_key = true;
+ }
+ else if(c == '=' && !escaped)
+ {
+ if(reading_key == false)
+ throw Invalid_Argument("Bad KV spec unexpected equals sign");
+ reading_key = false;
+ }
+ else
+ {
+ if(reading_key)
+ cur_key += c;
+ else
+ cur_val += c;
+
+ if(escaped)
+ escaped = false;
+ }
+ }
+
+ if(!cur_key.empty())
+ {
+ if(reading_key == false)
+ {
+ if(m.find(cur_key) != m.end())
+ throw Invalid_Argument("Bad KV spec duplicated key");
+ m[cur_key] = cur_val;
+ }
+ else
+ throw Invalid_Argument("Bad KV spec incomplete string");
+ }
+
+ return m;
+ }
+
+}
diff --git a/src/tests/data/utils/read_kv.vec b/src/tests/data/utils/read_kv.vec
new file mode 100644
index 000000000..c8c970ac6
--- /dev/null
+++ b/src/tests/data/utils/read_kv.vec
@@ -0,0 +1,50 @@
+[Valid]
+Input =
+Expected =
+
+Input = K=V
+Expected = K|V
+
+Input = K=V,K2=C\,omma
+Expected = K|V|K2|C,omma
+
+Input = K=V,K2=C\,omma,Eq=1\=1\,orsoihear
+Expected = K|V|K2|C,omma|Eq|1=1,orsoihear
+
+Input = Key=Escape\\fromNewYork
+Expected = Key|Escape\fromNewYork
+
+Input = Key=,Empty=SoVery
+Expected = Key||Empty|SoVery
+
+Input = TheKey\==Equals,Normal=OK
+Expected = TheKey=|Equals|Normal|OK
+
+[Invalid]
+
+Input = K=V,K2=V,K=V2
+Expected = Bad KV spec duplicated key
+
+Input = K=V,K2=V,K2=V2,K=V2
+Expected = Bad KV spec duplicated key
+
+Input = K=V,,K2=V
+Expected = Bad KV spec empty key
+
+Input = K==V,K2=V
+Expected = Bad KV spec unexpected equals sign
+
+Input = K=V,K2==V
+Expected = Bad KV spec unexpected equals sign
+
+Input = K=V,K2=V,
+Expected = Bad KV spec
+
+Input = K=V,K2=V,Wut
+Expected = Bad KV spec incomplete string
+
+Input = =V,K2=V
+Expected = Bad KV spec empty key
+
+Input = V
+Expected = Bad KV spec incomplete string
diff --git a/src/tests/test_utils.cpp b/src/tests/test_utils.cpp
index 6b082a474..5c231c2cd 100644
--- a/src/tests/test_utils.cpp
+++ b/src/tests/test_utils.cpp
@@ -632,6 +632,83 @@ class Hostname_Tests final : public Text_Based_Test
BOTAN_REGISTER_TEST("hostname", Hostname_Tests);
+class ReadKV_Tests final : public Text_Based_Test
+ {
+ public:
+ ReadKV_Tests() : Text_Based_Test("utils/read_kv.vec", "Input,Expected") {}
+
+ Test::Result run_one_test(const std::string& status, const VarMap& vars) override
+ {
+ Test::Result result("read_kv");
+
+ const bool is_valid = (status == "Valid");
+
+ const std::string input = vars.get_req_str("Input");
+ const std::string expected = vars.get_req_str("Expected");
+
+ if(is_valid)
+ {
+ confirm_kv(result, Botan::read_kv(input), split_group(expected));
+ }
+ else
+ {
+ // In this case "expected" is the expected exception message
+ result.test_throws("Invalid key value input throws exception",
+ expected,
+ [&]() { Botan::read_kv(input); });
+ }
+ return result;
+ }
+
+ private:
+
+ std::vector<std::string> split_group(const std::string& str)
+ {
+ std::vector<std::string> elems;
+ if(str.empty()) return elems;
+
+ std::string substr;
+ for(auto i = str.begin(); i != str.end(); ++i)
+ {
+ if(*i == '|')
+ {
+ elems.push_back(substr);
+ substr.clear();
+ }
+ else
+ {
+ substr += *i;
+ }
+ }
+
+ if(!substr.empty())
+ elems.push_back(substr);
+
+ return elems;
+ }
+
+ void confirm_kv(Test::Result& result,
+ const std::map<std::string, std::string>& kv,
+ const std::vector<std::string>& expected)
+ {
+ if(!result.test_eq("expected size", expected.size() % 2, size_t(0)))
+ return;
+
+ for(size_t i = 0; i != expected.size(); i += 2)
+ {
+ auto j = kv.find(expected[i]);
+ if(result.confirm("Found key", j != kv.end()))
+ {
+ result.test_eq("Matching value", j->second, expected[i+1]);
+ }
+ }
+
+ result.test_eq("KV has same size as expected", kv.size(), expected.size()/2);
+ }
+ };
+
+BOTAN_REGISTER_TEST("util_read_kv", ReadKV_Tests);
+
class CPUID_Tests final : public Test
{
public: