From f9802f686007e7efeaa88cba31f647ca96f1ee0c Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Thu, 8 Mar 2018 22:12:28 +0100 Subject: [PATCH] Added max_depth parameter to all to_/from_ methods --- include/fc/io/json.hpp | 48 +++++++++-------- include/fc/io/json_relaxed.hpp | 19 +++---- src/io/json.cpp | 99 +++++++++++++++++----------------- tests/io/json_tests.cpp | 11 ++++ 4 files changed, 96 insertions(+), 81 deletions(-) diff --git a/include/fc/io/json.hpp b/include/fc/io/json.hpp index d7c1b0f..21d07c4 100644 --- a/include/fc/io/json.hpp +++ b/include/fc/io/json.hpp @@ -2,6 +2,8 @@ #include #include +#define DEFAULT_MAX_RECURSION_DEPTH 200 + namespace fc { class ostream; @@ -33,52 +35,54 @@ namespace fc #endif }; - static ostream& to_stream( ostream& out, const fc::string&); - static ostream& to_stream( ostream& out, const variant& v, output_formatting format = stringify_large_ints_and_doubles ); - static ostream& to_stream( ostream& out, const variants& v, output_formatting format = stringify_large_ints_and_doubles ); - static ostream& to_stream( ostream& out, const variant_object& v, output_formatting format = stringify_large_ints_and_doubles ); + static ostream& to_stream( ostream& out, const fc::string& ); + static ostream& to_stream( ostream& out, const variant& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); + static ostream& to_stream( ostream& out, const variants& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); + static ostream& to_stream( ostream& out, const variant_object& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); - static variant from_stream( buffered_istream& in, parse_type ptype = legacy_parser ); + static variant from_stream( buffered_istream& in, parse_type ptype = legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); - static variant from_string( const string& utf8_str, parse_type ptype = legacy_parser ); - static variants variants_from_string( const string& utf8_str, parse_type ptype = legacy_parser ); - static string to_string( const variant& v, output_formatting format = stringify_large_ints_and_doubles ); - static string to_pretty_string( const variant& v, output_formatting format = stringify_large_ints_and_doubles ); + static variant from_string( const string& utf8_str, parse_type ptype = legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); + static variants variants_from_string( const string& utf8_str, parse_type ptype = legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); + static string to_string( const variant& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); + static string to_pretty_string( const variant& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); - static bool is_valid( const std::string& json_str, parse_type ptype = legacy_parser ); + static bool is_valid( const std::string& json_str, parse_type ptype = legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); template - static void save_to_file( const T& v, const fc::path& fi, bool pretty = true, output_formatting format = stringify_large_ints_and_doubles ) + static void save_to_file( const T& v, const fc::path& fi, bool pretty = true, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ) { - save_to_file( variant(v), fi, pretty, format ); + save_to_file( variant(v), fi, pretty, format, max_depth ); } - static void save_to_file( const variant& v, const fc::path& fi, bool pretty = true, output_formatting format = stringify_large_ints_and_doubles ); - static variant from_file( const fc::path& p, parse_type ptype = legacy_parser ); + static void save_to_file( const variant& v, const fc::path& fi, bool pretty = true, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); + static variant from_file( const fc::path& p, parse_type ptype = legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); template - static T from_file( const fc::path& p, parse_type ptype = legacy_parser ) + static T from_file( const fc::path& p, parse_type ptype = legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ) { - return json::from_file(p, ptype).as(); + return json::from_file(p, ptype, max_depth).as(); } template - static string to_string( const T& v, output_formatting format = stringify_large_ints_and_doubles ) + static string to_string( const T& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ) { - return to_string( variant(v), format ); + return to_string( variant(v), format, max_depth ); } template - static string to_pretty_string( const T& v, output_formatting format = stringify_large_ints_and_doubles ) + static string to_pretty_string( const T& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ) { - return to_pretty_string( variant(v), format ); + return to_pretty_string( variant(v), format, max_depth ); } template - static void save_to_file( const T& v, const std::string& p, bool pretty = true, output_formatting format = stringify_large_ints_and_doubles ) + static void save_to_file( const T& v, const std::string& p, bool pretty = true, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ) { - save_to_file( variant(v), fc::path(p), pretty ); + save_to_file( variant(v), fc::path(p), pretty, format, max_depth ); } }; } // fc + +#undef DEFAULT_MAX_RECURSION_DEPTH diff --git a/include/fc/io/json_relaxed.hpp b/include/fc/io/json_relaxed.hpp index a9196c2..555b21a 100644 --- a/include/fc/io/json_relaxed.hpp +++ b/include/fc/io/json_relaxed.hpp @@ -21,7 +21,7 @@ namespace fc { namespace json_relaxed { template - variant variant_from_stream( T& in, uint32_t depth ); + variant variant_from_stream( T& in, uint32_t max_depth ); template fc::string tokenFromStream( T& in ) @@ -569,17 +569,17 @@ namespace fc { namespace json_relaxed } FC_CAPTURE_AND_RETHROW( (token) ) } template - variant_object objectFromStream( T& in, uint32_t depth ) + variant_object objectFromStream( T& in, uint32_t max_depth ) { std::function get_key = []( T& in ){ return json_relaxed::stringFromStream( in ); }; - std::function get_value = [depth]( T& in ){ return json_relaxed::variant_from_stream( in, depth ); }; + std::function get_value = [max_depth]( T& in ){ return json_relaxed::variant_from_stream( in, max_depth ); }; return objectFromStreamBase( in, get_key, get_value ); } template - variants arrayFromStream( T& in, uint32_t depth ) + variants arrayFromStream( T& in, uint32_t max_depth ) { - std::function get_value = [depth]( T& in ){ return json_relaxed::variant_from_stream( in, depth ); }; + std::function get_value = [max_depth]( T& in ){ return json_relaxed::variant_from_stream( in, max_depth ); }; return arrayFromStreamBase( in, get_value ); } @@ -625,9 +625,10 @@ namespace fc { namespace json_relaxed } template - variant variant_from_stream( T& in, uint32_t depth ) + variant variant_from_stream( T& in, uint32_t max_depth ) { - FC_ASSERT( depth < MAX_RECURSION_DEPTH, "Too many nested items in JSON string!" ); + if( max_depth == 0 ) + FC_THROW_EXCEPTION( parse_error_exception, "Too many nested items in JSON input!" ); skip_white_space(in); signed char c = in.peek(); switch( c ) @@ -635,9 +636,9 @@ namespace fc { namespace json_relaxed case '"': return json_relaxed::stringFromStream( in ); case '{': - return json_relaxed::objectFromStream( in, depth + 1 ); + return json_relaxed::objectFromStream( in, max_depth - 1 ); case '[': - return json_relaxed::arrayFromStream( in, depth + 1 ); + return json_relaxed::arrayFromStream( in, max_depth - 1 ); case '-': case '+': case '.': diff --git a/src/io/json.cpp b/src/io/json.cpp index 9553ff3..a0f484e 100644 --- a/src/io/json.cpp +++ b/src/io/json.cpp @@ -14,21 +14,21 @@ namespace fc { // forward declarations of provided functions - template variant variant_from_stream( T& in, uint32_t depth = 0 ); + template variant variant_from_stream( T& in, uint32_t max_depth ); template char parseEscape( T& in ); template fc::string stringFromStream( T& in ); template bool skip_white_space( T& in ); template fc::string stringFromToken( T& in ); template variant_object objectFromStreamBase( T& in, std::function& get_key, std::function& get_value ); - template variant_object objectFromStream( T& in, uint32_t depth ); + template variant_object objectFromStream( T& in, uint32_t max_depth ); template variants arrayFromStreamBase( T& in, std::function& get_value ); - template variants arrayFromStream( T& in, uint32_t depth ); + template variants arrayFromStream( T& in, uint32_t max_depth ); template variant number_from_stream( T& in ); template variant token_from_stream( T& in ); void escape_string( const string& str, ostream& os ); - template void to_stream( T& os, const variants& a, json::output_formatting format ); - template void to_stream( T& os, const variant_object& o, json::output_formatting format ); - template void to_stream( T& os, const variant& v, json::output_formatting format ); + template void to_stream( T& os, const variants& a, json::output_formatting format, uint32_t max_depth ); + template void to_stream( T& os, const variant_object& o, json::output_formatting format, uint32_t max_depth ); + template void to_stream( T& os, const variant& v, json::output_formatting format, uint32_t max_depth ); fc::string pretty_print( const fc::string& v, uint8_t indent ); } @@ -38,8 +38,6 @@ namespace fc #define FALLTHROUGH #endif -#define MAX_RECURSION_DEPTH 200 - #include namespace fc @@ -226,10 +224,10 @@ namespace fc } template - variant_object objectFromStream( T& in, uint32_t depth ) + variant_object objectFromStream( T& in, uint32_t max_depth ) { std::function get_key = []( T& in ){ return stringFromStream( in ); }; - std::function get_value = [depth]( T& in ){ return variant_from_stream( in, depth ); }; + std::function get_value = [max_depth]( T& in ){ return variant_from_stream( in, max_depth ); }; return objectFromStreamBase( in, get_key, get_value ); } @@ -264,9 +262,9 @@ namespace fc } template - variants arrayFromStream( T& in, uint32_t depth ) + variants arrayFromStream( T& in, uint32_t max_depth ) { - std::function get_value = [depth]( T& in ){ return variant_from_stream( in, depth ); }; + std::function get_value = [max_depth]( T& in ){ return variant_from_stream( in, max_depth ); }; return arrayFromStreamBase( in, get_value ); } @@ -411,9 +409,9 @@ namespace fc template - variant variant_from_stream( T& in, uint32_t depth ) + variant variant_from_stream( T& in, uint32_t max_depth ) { - if( depth > MAX_RECURSION_DEPTH ) + if( max_depth == 0 ) FC_THROW_EXCEPTION( parse_error_exception, "Too many nested items in JSON input!" ); skip_white_space(in); signed char c = in.peek(); @@ -422,9 +420,9 @@ namespace fc case '"': return stringFromStream( in ); case '{': - return objectFromStream( in, depth + 1 ); + return objectFromStream( in, max_depth - 1 ); case '[': - return arrayFromStream( in, depth + 1 ); + return arrayFromStream( in, max_depth - 1 ); case '-': case '.': case '0': @@ -456,20 +454,20 @@ namespace fc } } - variant json::from_string( const std::string& utf8_str, parse_type ptype ) + variant json::from_string( const std::string& utf8_str, parse_type ptype, uint32_t max_depth ) { try { fc::istream_ptr in( new fc::stringstream( utf8_str ) ); fc::buffered_istream bin( in ); - return from_stream( bin, ptype ); + return from_stream( bin, ptype, max_depth ); } FC_RETHROW_EXCEPTIONS( warn, "", ("str",utf8_str) ) } - variants json::variants_from_string( const std::string& utf8_str, parse_type ptype ) + variants json::variants_from_string( const std::string& utf8_str, parse_type ptype, uint32_t max_depth ) { variants result; try { fc::stringstream in( utf8_str ); while( true ) - result.push_back(json_relaxed::variant_from_stream( in, 0 )); + result.push_back(json_relaxed::variant_from_stream( in, max_depth )); } catch ( const fc::eof_exception& ) { return result; } FC_RETHROW_EXCEPTIONS( warn, "", ("str",utf8_str) ) @@ -555,14 +553,14 @@ namespace fc } template - void to_stream( T& os, const variants& a, json::output_formatting format ) + void to_stream( T& os, const variants& a, json::output_formatting format, uint32_t max_depth ) { os << '['; auto itr = a.begin(); while( itr != a.end() ) { - to_stream( os, *itr, format ); + to_stream( os, *itr, format, max_depth ); ++itr; if( itr != a.end() ) os << ','; @@ -570,7 +568,7 @@ namespace fc os << ']'; } template - void to_stream( T& os, const variant_object& o, json::output_formatting format ) + void to_stream( T& os, const variant_object& o, json::output_formatting format, uint32_t max_depth ) { os << '{'; auto itr = o.begin(); @@ -579,7 +577,7 @@ namespace fc { escape_string( itr->key(), os ); os << ':'; - to_stream( os, itr->value(), format ); + to_stream( os, itr->value(), format, max_depth ); ++itr; if( itr != o.end() ) os << ','; @@ -588,8 +586,9 @@ namespace fc } template - void to_stream( T& os, const variant& v, json::output_formatting format ) + void to_stream( T& os, const variant& v, json::output_formatting format, uint32_t max_depth ) { + FC_ASSERT( max_depth > 0, "Too many nested objects!" ); switch( v.get_type() ) { case variant::null_type: @@ -625,20 +624,20 @@ namespace fc escape_string( v.as_string(), os ); return; case variant::array_type: - to_stream( os, v.get_array(), format ); + to_stream( os, v.get_array(), format, max_depth - 1 ); return; case variant::object_type: - to_stream(os, v.get_object(), format ); + to_stream(os, v.get_object(), format, max_depth - 1 ); return; default: FC_THROW_EXCEPTION( fc::invalid_arg_exception, "Unsupported variant type: " + v.get_type() ); } } - fc::string json::to_string( const variant& v, output_formatting format /* = stringify_large_ints_and_doubles */ ) + fc::string json::to_string( const variant& v, output_formatting format, uint32_t max_depth ) { fc::stringstream ss; - fc::to_stream( ss, v, format ); + fc::to_stream( ss, v, format, max_depth ); return ss.str(); } @@ -733,74 +732,74 @@ namespace fc - fc::string json::to_pretty_string( const variant& v, output_formatting format /* = stringify_large_ints_and_doubles */ ) + fc::string json::to_pretty_string( const variant& v, output_formatting format, uint32_t max_depth ) { - return pretty_print(to_string(v, format), 2); + return pretty_print(to_string(v, format, max_depth), 2); } - void json::save_to_file( const variant& v, const fc::path& fi, bool pretty, output_formatting format /* = stringify_large_ints_and_doubles */ ) + void json::save_to_file( const variant& v, const fc::path& fi, bool pretty, output_formatting format, uint32_t max_depth ) { if( pretty ) { - auto str = json::to_pretty_string( v, format ); + auto str = json::to_pretty_string( v, format, max_depth ); fc::ofstream o(fi); o.write( str.c_str(), str.size() ); } else { fc::ofstream o(fi); - fc::to_stream( o, v, format ); + fc::to_stream( o, v, format, max_depth ); } } - variant json::from_file( const fc::path& p, parse_type ptype ) + variant json::from_file( const fc::path& p, parse_type ptype, uint32_t max_depth ) { fc::istream_ptr in( new fc::ifstream( p ) ); fc::buffered_istream bin( in ); - return from_stream( bin, ptype ); + return from_stream( bin, ptype, max_depth ); } - variant json::from_stream( buffered_istream& in, parse_type ptype ) + variant json::from_stream( buffered_istream& in, parse_type ptype, uint32_t max_depth ) { switch( ptype ) { case legacy_parser: - return variant_from_stream( in ); + return variant_from_stream( in, max_depth ); #ifdef WITH_EXOTIC_JSON_PARSERS case legacy_parser_with_string_doubles: - return variant_from_stream( in ); + return variant_from_stream( in, max_depth ); case strict_parser: - return json_relaxed::variant_from_stream( in ); + return json_relaxed::variant_from_stream( in, max_depth ); case relaxed_parser: - return json_relaxed::variant_from_stream( in ); + return json_relaxed::variant_from_stream( in, max_depth ); #endif case broken_nul_parser: - return variant_from_stream( in ); + return variant_from_stream( in, max_depth ); default: FC_ASSERT( false, "Unknown JSON parser type {ptype}", ("ptype", ptype) ); } } - ostream& json::to_stream( ostream& out, const variant& v, output_formatting format /* = stringify_large_ints_and_doubles */ ) + ostream& json::to_stream( ostream& out, const variant& v, output_formatting format, uint32_t max_depth ) { - fc::to_stream( out, v, format ); + fc::to_stream( out, v, format, max_depth ); return out; } - ostream& json::to_stream( ostream& out, const variants& v, output_formatting format /* = stringify_large_ints_and_doubles */ ) + ostream& json::to_stream( ostream& out, const variants& v, output_formatting format, uint32_t max_depth ) { - fc::to_stream( out, v, format ); + fc::to_stream( out, v, format, max_depth ); return out; } - ostream& json::to_stream( ostream& out, const variant_object& v, output_formatting format /* = stringify_large_ints_and_doubles */ ) + ostream& json::to_stream( ostream& out, const variant_object& v, output_formatting format, uint32_t max_depth ) { - fc::to_stream( out, v, format ); + fc::to_stream( out, v, format, max_depth ); return out; } - bool json::is_valid( const std::string& utf8_str, parse_type ptype ) + bool json::is_valid( const std::string& utf8_str, parse_type ptype, uint32_t max_depth ) { if( utf8_str.size() == 0 ) return false; fc::istream_ptr in( new fc::stringstream( utf8_str ) ); fc::buffered_istream bin( in ); - from_stream( bin, ptype ); + from_stream( bin, ptype, max_depth ); try { bin.peek(); } catch ( const eof_exception& e ) { return true; } return false; } diff --git a/tests/io/json_tests.cpp b/tests/io/json_tests.cpp index 9be97fe..e5421f2 100644 --- a/tests/io/json_tests.cpp +++ b/tests/io/json_tests.cpp @@ -321,4 +321,15 @@ BOOST_AUTO_TEST_CASE(precision_test) BOOST_CHECK_EQUAL( "0.5", half ); } +BOOST_AUTO_TEST_CASE(recursion_test) +{ + std::string ten_levels = "[[[[[[[[[[]]]]]]]]]]"; + fc::variant nested = fc::json::from_string( ten_levels ); + BOOST_CHECK_THROW( fc::json::from_string( ten_levels, fc::json::legacy_parser, 9 ), fc::parse_error_exception ); + + std::string back = fc::json::to_string( nested ); + BOOST_CHECK_EQUAL( ten_levels, back ); + BOOST_CHECK_THROW( fc::json::to_string( nested, fc::json::stringify_large_ints_and_doubles, 9 ), fc::assert_exception ); +} + BOOST_AUTO_TEST_SUITE_END()