diff options
-rw-r--r-- | src/lib/utils/parsing.h | 29 | ||||
-rw-r--r-- | src/lib/utils/read_kv.cpp | 85 | ||||
-rw-r--r-- | src/tests/data/utils/read_kv.vec | 50 | ||||
-rw-r--r-- | src/tests/test_utils.cpp | 77 |
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: |