Implement generic tryTo for string to boolean converions (#4689)

Implement generic `tryTo` for string to boolean converions
Also use it in some obvious placed in codebase
This commit is contained in:
Alexander 2018-07-12 15:07:36 +01:00 committed by GitHub
parent ed4354c9ef
commit d31e1bc2e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 115 additions and 12 deletions

View File

@ -10,10 +10,12 @@
#include <iomanip>
#include <locale>
#include <unordered_map>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/io/detail/quoted_manip.hpp>
#if (BOOST_VERSION >= 106600)
#include <boost/uuid/detail/sha1.hpp>
#else
@ -418,4 +420,42 @@ std::string getBufferSHA1(const char* buffer, size_t size) {
size_t operator"" _sz(unsigned long long int x) {
return x;
}
namespace impl {
Expected<bool, ConversionError> stringToBool(std::string from) {
static const auto table = std::unordered_map<std::string, bool>{
{"1", true},
{"0", false},
{"y", true},
{"yes", true},
{"n", false},
{"no", false},
{"t", true},
{"true", true},
{"f", false},
{"false", false},
{"ok", true},
{"disable", false},
{"enable", true},
};
using CharType = std::string::value_type;
// Classic locale could be used here because all available string
// representations of boolean have ascii encoding. It must be a bit faster.
static const auto& ctype =
std::use_facet<std::ctype<CharType>>(std::locale::classic());
for (auto& ch : from) {
ch = ctype.tolower(ch);
}
const auto it = table.find(from);
if (it == table.end()) {
return createError(ConversionError::InvalidArgument,
"Wrong string representation of boolean ")
<< boost::io::quoted(from);
}
return it->second;
}
} // namespace impl
} // namespace osquery

View File

@ -322,4 +322,28 @@ inline typename std::enable_if<
tryTo(FromType&& from) {
return std::forward<FromType>(from);
}
namespace impl {
Expected<bool, ConversionError> stringToBool(std::string from);
} // namespace impl
/**
* Parsing general representation of boolean value in string.
* "1" : true
* "0" : false
* "y" : true
* "yes" : true
* "n" : false
* "no" : false
* ... and so on
* For the full list of possible valid values @see stringToBool definition
*/
template <typename ToType>
inline typename std::enable_if<std::is_same<ToType, bool>::value,
Expected<ToType, ConversionError>>::type
tryTo(std::string from) {
return impl::stringToBool(std::move(from));
}
}

View File

@ -324,4 +324,45 @@ TEST_F(ConversionsTests, tryTo_same_type) {
auto ret2 = tryTo<First>(const_test_lvalue);
ASSERT_FALSE(ret2.isError());
}
TEST_F(ConversionsTests, tryTo_string_to_boolean_valid_args) {
const auto test_table = std::unordered_map<std::string, bool>{
{"1", true}, {"0", false}, {"y", true},
{"n", false}, {"yes", true}, {"yEs", true},
{"Yes", true}, {"no", false}, {"No", false},
{"t", true}, {"T", true}, {"f", false},
{"F", false}, {"true", true}, {"True", true},
{"tRUE", true}, {"false", false}, {"fALse", false},
{"ok", true}, {"OK", true}, {"Ok", true},
{"enable", true}, {"Enable", true}, {"ENABLE", true},
{"disable", false}, {"Disable", false}, {"DISABLE", false},
};
for (const auto& argAndAnswer : test_table) {
auto exp = tryTo<bool>(argAndAnswer.first);
ASSERT_FALSE(exp.isError());
EXPECT_EQ(argAndAnswer.second, exp.get());
}
}
TEST_F(ConversionsTests, tryTo_string_to_boolean_invalid_args) {
const auto test_table = std::vector<std::string>{
"", "\0", "\n", "\x06", "\x15", "\x27", "ADS",
"7251", "20.09", "M0V+K7V", "+", "-", ".", "@",
"1.0", "11", "00", " 0", "1 ", "2", "10",
"100%", "_0", "1_", "1.", "2.", "E", "a",
"b", "d", "e", "o", "p", "uh", "nix",
"nixie", "nixy", "nixey", "nay", "nah", "no way", "veto",
"yea", "yeah", "yep", "okey", "aye", "roger", "uh-huh",
"righto", "yup", "yuppers", "ja", "surely", "amen", "totally",
"sure", "yessir", "true.", "tru", "tr", "tr.", "ff",
"yy", "nn", "nope", "null", "nil", "dis", "able",
"pos", "neg", "ack", "ACK", "NAK", "enabled", "disabled",
"valid", "invalid", "void", "allow", "permit", "positive", "negative",
};
for (const auto& wrong : test_table) {
auto exp = tryTo<bool>(wrong);
ASSERT_TRUE(exp.isError());
EXPECT_EQ(ConversionError::InvalidArgument, exp.getErrorCode());
}
}
} // namespace osquery

View File

@ -38,6 +38,7 @@
#include <osquery/packs.h>
#include <osquery/registry_factory.h>
#include "osquery/core/conversions.h"
#include "osquery/core/process.h"
#include "osquery/devtools/devtools.h"
#include "osquery/filesystem/fileops.h"
@ -1049,15 +1050,12 @@ static int booleanValue(char* zArg) {
if (i > 0 && zArg[i] == 0) {
return static_cast<int>(integerValue(zArg) & 0xffffffff);
}
if (sqlite3_stricmp(zArg, "on") == 0 || sqlite3_stricmp(zArg, "yes") == 0) {
return 1;
auto expected = osquery::tryTo<bool>(std::string{zArg});
if (expected.isError()) {
fprintf(
stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", zArg);
}
if (sqlite3_stricmp(zArg, "off") == 0 || sqlite3_stricmp(zArg, "no") == 0) {
return 0;
}
fprintf(
stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", zArg);
return 0;
return expected.get_or(false) ? 1 : 0;
}
inline void meta_tables(int nArg, char** azArg) {

View File

@ -19,6 +19,7 @@
#pragma warning(pop)
#endif
#include <osquery/core/conversions.h>
#include <osquery/filesystem.h>
#include <osquery/tables.h>
@ -33,10 +34,9 @@ QueryData genChromeBasedExtensions(QueryContext& context,
/// A helper check to rename bool-type values as 1 or 0.
inline void jsonBoolAsInt(std::string& s) {
if (s == "true" || s == "YES" || s == "Yes") {
s = "1";
} else if (s == "false" || s == "NO" || s == "No") {
s = "0";
auto expected = tryTo<bool>(s);
if (!expected.isError()) {
s = expected.get() ? "1" : "0";
}
}
}