2019-11-13 14:39:29 +00:00
# include "StdAfx.h"
# include "FormElement.h"
2019-10-31 12:11:49 +00:00
namespace MWR : : C3 : : Linter
{
namespace
{
2019-11-18 12:57:56 +00:00
/// Validate input length against argument rules
/// @param argument definition
/// @returns input to validate
/// @throws std::invalid_argument if validation fails
2019-10-31 12:11:49 +00:00
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 ( ) ) + ' ) ' ) ;
}
2019-11-18 12:57:56 +00:00
/// Validate numeric value against argument rules
/// @param argument definition
/// @returns value to validate
/// @throws std::invalid_argument if validation fails
2019-10-31 12:11:49 +00:00
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 ) + ' ) ' ) ;
} ;
2019-11-18 12:57:56 +00:00
/// Concrete implementation for boolean argument type
2019-10-31 12:11:49 +00:00
class BooleanFormElement : public FormElement
{
public :
BooleanFormElement ( json & element ) : FormElement ( element )
{
}
2019-11-18 12:57:56 +00:00
/// Set boolean value from input accepted values interpreted as true are "true", "yes", "y", "1"
/// @param input to validate
2019-10-31 12:11:49 +00:00
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 ; } ) ;
}
} ;
2019-11-18 12:57:56 +00:00
/// Concrete implementation for string argument type
2019-10-31 12:11:49 +00:00
class StringFormElement : public FormElement
{
public :
StringFormElement ( json & element ) : FormElement ( element )
{
}
2019-11-18 12:57:56 +00:00
/// Validate and set string value from input
/// @param input to validate
/// @throws std::invalid_argument if validation fails
2019-10-31 12:11:49 +00:00
void ValidateAndSet ( std : : string_view input ) override
{
CheckLengthConstraint ( m_Definition , input ) ;
m_Definition [ " value " ] = input ;
}
} ;
2019-11-18 12:57:56 +00:00
/// Concrete implementation for ip argument type
2019-10-31 12:11:49 +00:00
class IpFormElement : public FormElement
{
public :
IpFormElement ( json & element ) : FormElement ( element )
{
}
2019-11-18 12:57:56 +00:00
/// Validate and set IP value from input
/// @param input to validate
/// @throws std::invalid_argument if validation fails
2019-10-31 12:11:49 +00:00
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 ;
}
2019-11-18 12:57:56 +00:00
/// Determine if input is an IPv4
/// @param input to validate
/// @returns true if input is in IPv4 dotted notation
2019-10-31 12:11:49 +00:00
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 ;
}
}
} ;
2019-11-18 12:57:56 +00:00
/// Concrete implementation for binary argument type
2019-10-31 12:11:49 +00:00
class BinaryFormElement : public FormElement
{
public :
BinaryFormElement ( json & element ) : FormElement ( element )
{
}
2019-11-18 12:57:56 +00:00
/// Validate and set binary value from input (in base64 string)
/// @param input to validate
/// @throws std::invalid_argument if validation fails
2019-10-31 12:11:49 +00:00
void ValidateAndSet ( std : : string_view input ) override
{
2019-11-18 12:57:56 +00:00
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 ) + ' " ' ) ;
}
2019-10-31 12:11:49 +00:00
}
} ;
2019-11-18 12:57:56 +00:00
/// Concrete implementation for binary argument type
/// @tparam Numeric - type of numeric argument
// TODO constrain Numeric template parameter to numeric types only
2019-10-31 12:11:49 +00:00
template < typename Numeric >
class NumericFormElement : public FormElement
{
public :
NumericFormElement ( json & element ) : FormElement ( element )
{
}
2019-11-18 12:57:56 +00:00
/// Validate and set numeric value from input
/// @param input to validate
/// @throws std::invalid_argument if validation fails
2019-10-31 12:11:49 +00:00
void ValidateAndSet ( std : : string_view input ) override
{
2019-11-18 12:57:56 +00:00
Numeric value ;
2019-10-31 12:11:49 +00:00
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 ;
}
} ;
2019-11-13 14:39:29 +00:00
}
FormElement : : FormElement ( json & definition ) :
m_Definition ( definition )
{
}
2019-10-31 12:11:49 +00:00
2019-11-18 12:57:56 +00:00
/// Generate FormElement::Type to_json and from_json fuctions to enable serialization
2019-11-13 14:39:29 +00:00
NLOHMANN_JSON_SERIALIZE_ENUM
(
FormElement : : Type ,
2019-10-31 12:11:49 +00:00
{
2019-11-13 14:39:29 +00:00
{ 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 " } ,
2019-10-31 12:11:49 +00:00
}
2019-11-13 14:39:29 +00:00
) ;
2019-10-31 12:11:49 +00:00
2019-11-13 14:39:29 +00:00
std : : unique_ptr < FormElement > MakeFormElement ( json & element )
{
if ( ! element . is_object ( ) )
throw std : : invalid_argument { " Form element must be a json object. " } ;
2020-02-18 15:09:28 +00:00
if ( ! element . contains ( " name " ) )
throw std : : invalid_argument { " Form element must contain 'name' property. \n Invalid element: \n " + element . dump ( 4 ) } ;
2019-11-13 14:39:29 +00:00
if ( ! element . contains ( " type " ) )
2020-02-18 15:09:28 +00:00
throw std : : invalid_argument { " Form element ' " + element [ " name " ] . get < std : : string > ( ) + " ' must contain 'type' property. " } ;
2019-11-13 14:39:29 +00:00
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 > ( ) + ' " ' ) ;
}
2019-10-31 12:11:49 +00:00
}
2019-11-13 14:39:29 +00:00
}