mirror of https://github.com/infosecn1nja/C3.git
245 lines
8.5 KiB
C++
245 lines
8.5 KiB
C++
#include "StdAfx.h"
|
|
#include "FormElement.h"
|
|
|
|
namespace FSecure::C3::Linter
|
|
{
|
|
namespace
|
|
{
|
|
/// Validate input length against argument rules
|
|
/// @param argument definition
|
|
/// @returns input to validate
|
|
/// @throws std::invalid_argument if validation fails
|
|
inline void CheckLengthConstraint(json const& definition, std::string_view input)
|
|
{
|
|
if (definition.contains("min") && input.length() < definition["min"].get<size_t>())
|
|
throw std::invalid_argument("Input \""s + std::string(input) + "\" too short for argument " + definition.at("name").get<std::string>() + ". (min = " + std::to_string(definition["min"].get<size_t>()) + ", actual = " + std::to_string(input.length()) + ')');
|
|
|
|
if (definition.contains("max") && input.length() > definition["max"].get<size_t>())
|
|
throw std::invalid_argument("Input \""s + std::string(input) + "\" too long for argument " + definition.at("name").get<std::string>() + ". (max = " + std::to_string(definition["max"].get<size_t>()) + ", actual = " + std::to_string(input.length()) + ')');
|
|
}
|
|
|
|
/// Validate numeric value against argument rules
|
|
/// @param argument definition
|
|
/// @returns value to validate
|
|
/// @throws std::invalid_argument if validation fails
|
|
template<typename Numeric>
|
|
void CheckValueConstraint(json const& definition, Numeric value)
|
|
{
|
|
if (definition.contains("min") && value < definition["min"])
|
|
throw std::invalid_argument("Value too low for argument " + definition.at("name").get<std::string>() + ". (min = " + std::to_string(definition["min"].get<Numeric>()) + ", actual = " + std::to_string(value) + ')');
|
|
|
|
if (definition.contains("max") && value > definition["max"])
|
|
throw std::invalid_argument("Value too high for argument " + definition.at("name").get<std::string>() + ". (min = " + std::to_string(definition["min"].get<Numeric>()) + ", actual = " + std::to_string(value) + ')');
|
|
};
|
|
|
|
/// Concrete implementation for boolean argument type
|
|
class BooleanFormElement : public FormElement
|
|
{
|
|
public:
|
|
BooleanFormElement(json& element) : FormElement(element)
|
|
{
|
|
}
|
|
|
|
/// Destructor
|
|
virtual ~BooleanFormElement() = default;
|
|
|
|
/// Set boolean value from input accepted values interpreted as true are "true", "yes", "y", "1"
|
|
/// @param input to validate
|
|
void ValidateAndSet(std::string_view input) override
|
|
{
|
|
std::string lowercase(input);
|
|
std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), ::tolower);
|
|
auto trueOptions = { "true", "yes", "y", "1" };
|
|
m_Definition["value"] = std::any_of(begin(trueOptions), end(trueOptions), [&lowercase](auto&& trueOption) {return lowercase == trueOption; });
|
|
}
|
|
};
|
|
|
|
/// Concrete implementation for string argument type
|
|
class StringFormElement : public FormElement
|
|
{
|
|
public:
|
|
StringFormElement(json& element) : FormElement(element)
|
|
{
|
|
}
|
|
|
|
/// Destructor
|
|
virtual ~StringFormElement() = default;
|
|
|
|
|
|
/// Validate and set string value from input
|
|
/// @param input to validate
|
|
/// @throws std::invalid_argument if validation fails
|
|
void ValidateAndSet(std::string_view input) override
|
|
{
|
|
CheckLengthConstraint(m_Definition, input);
|
|
m_Definition["value"] = input;
|
|
}
|
|
};
|
|
|
|
/// Concrete implementation for ip argument type
|
|
class IpFormElement : public FormElement
|
|
{
|
|
public:
|
|
IpFormElement(json& element) : FormElement(element)
|
|
{
|
|
}
|
|
|
|
/// Destructor
|
|
virtual ~IpFormElement() = default;
|
|
|
|
/// Validate and set IP value from input
|
|
/// @param input to validate
|
|
/// @throws std::invalid_argument if validation fails
|
|
void ValidateAndSet(std::string_view input) override
|
|
{
|
|
if (!IsIpV4(std::string(input)))
|
|
throw std::invalid_argument("Failed to IPv4 value from input ("s + std::string(input) + ')');
|
|
m_Definition["value"] = input;
|
|
}
|
|
|
|
/// Determine if input is an IPv4
|
|
/// @param input to validate
|
|
/// @returns true if input is in IPv4 dotted notation
|
|
static bool IsIpV4(std::string const& input)
|
|
{
|
|
sockaddr_in client;
|
|
client.sin_family = AF_INET;
|
|
switch (InetPtonA(AF_INET, input.c_str(), &client.sin_addr.s_addr))
|
|
{
|
|
case 1:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Concrete implementation for binary argument type
|
|
class BinaryFormElement : public FormElement
|
|
{
|
|
public:
|
|
BinaryFormElement(json& element) : FormElement(element)
|
|
{
|
|
}
|
|
|
|
/// Destructor
|
|
virtual ~BinaryFormElement() = default;
|
|
|
|
/// Validate and set binary value from input (in base64 string)
|
|
/// @param input to validate
|
|
/// @throws std::invalid_argument if validation fails
|
|
void ValidateAndSet(std::string_view input) override
|
|
{
|
|
try
|
|
{
|
|
auto decoded = base64::decode<std::string>(input);
|
|
CheckLengthConstraint(m_Definition, decoded);
|
|
m_Definition["value"] = input;
|
|
}
|
|
catch (std::domain_error&)
|
|
{
|
|
throw std::invalid_argument("Failed to decode base64 encoded input \""s + std::string(input) + '"');
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
/// Concrete implementation for binary argument type
|
|
/// @tparam Numeric - type of numeric argument
|
|
// TODO constrain Numeric template parameter to numeric types only
|
|
template<typename Numeric>
|
|
class NumericFormElement : public FormElement
|
|
{
|
|
public:
|
|
NumericFormElement(json& element) : FormElement(element)
|
|
{
|
|
}
|
|
|
|
/// Destructor
|
|
virtual ~NumericFormElement() = default;
|
|
|
|
/// Validate and set numeric value from input
|
|
/// @param input to validate
|
|
/// @throws std::invalid_argument if validation fails
|
|
void ValidateAndSet(std::string_view input) override
|
|
{
|
|
Numeric value;
|
|
auto x = std::from_chars(input.data(), input.data() + input.size(), value);
|
|
if (x.ptr != input.data() + input.size())
|
|
throw std::invalid_argument("Failed to read numeric value from input \""s + std::string(input) + '"');
|
|
CheckValueConstraint(m_Definition, value);
|
|
m_Definition["value"] = value;
|
|
}
|
|
};
|
|
}
|
|
|
|
FormElement::FormElement(json& definition) :
|
|
m_Definition(definition)
|
|
{
|
|
}
|
|
|
|
/// Generate FormElement::Type to_json and from_json fuctions to enable serialization
|
|
NLOHMANN_JSON_SERIALIZE_ENUM
|
|
(
|
|
FormElement::Type,
|
|
{
|
|
{FormElement::Type::Unknown, nullptr},
|
|
{FormElement::Type::Uint8, "uint8"},
|
|
{FormElement::Type::Uint16, "uint16"},
|
|
{FormElement::Type::Uint32, "uint32"},
|
|
{FormElement::Type::Uint64, "uint64"},
|
|
{FormElement::Type::Int8, "int8"},
|
|
{FormElement::Type::Int16, "int16"},
|
|
{FormElement::Type::Int32, "int32"},
|
|
{FormElement::Type::Int64, "int64"},
|
|
{FormElement::Type::Float, "float"},
|
|
{FormElement::Type::Boolean, "boolean"},
|
|
{FormElement::Type::String, "string"},
|
|
{FormElement::Type::Ip, "ip"},
|
|
{FormElement::Type::Binary, "binary"},
|
|
}
|
|
);
|
|
|
|
std::unique_ptr<FormElement> MakeFormElement(json& element)
|
|
{
|
|
if (!element.is_object())
|
|
throw std::invalid_argument { "Form element must be a json object." };
|
|
if (!element.contains("name"))
|
|
throw std::invalid_argument{ "Form element must contain 'name' property. \nInvalid element:\n" + element.dump(4) };
|
|
if (!element.contains("type"))
|
|
throw std::invalid_argument{ "Form element '" + element["name"].get<std::string>() + "' must contain 'type' property." };
|
|
|
|
switch (element["type"].get<FormElement::Type>())
|
|
{
|
|
case FormElement::Type::Uint8:
|
|
return std::make_unique<NumericFormElement<uint8_t>>(element);
|
|
case FormElement::Type::Uint16:
|
|
return std::make_unique<NumericFormElement<uint16_t>>(element);
|
|
case FormElement::Type::Uint32:
|
|
return std::make_unique<NumericFormElement<uint32_t>>(element);
|
|
case FormElement::Type::Uint64:
|
|
return std::make_unique<NumericFormElement<uint64_t>>(element);
|
|
case FormElement::Type::Int8:
|
|
return std::make_unique<NumericFormElement<int8_t>>(element);
|
|
case FormElement::Type::Int16:
|
|
return std::make_unique<NumericFormElement<int16_t>>(element);
|
|
case FormElement::Type::Int32:
|
|
return std::make_unique<NumericFormElement<int32_t>>(element);
|
|
case FormElement::Type::Int64:
|
|
return std::make_unique<NumericFormElement<int64_t>>(element);
|
|
case FormElement::Type::Float:
|
|
return std::make_unique<NumericFormElement<float>>(element);
|
|
case FormElement::Type::Boolean:
|
|
return std::make_unique<BooleanFormElement>(element);
|
|
case FormElement::Type::String:
|
|
return std::make_unique<StringFormElement>(element);
|
|
case FormElement::Type::Ip:
|
|
return std::make_unique<IpFormElement>(element);
|
|
case FormElement::Type::Binary:
|
|
return std::make_unique<BinaryFormElement>(element);
|
|
default:
|
|
throw std::runtime_error("Unknown form argument type: \"" + element["type"].get<std::string>() + '"');
|
|
}
|
|
}
|
|
}
|