diff --git a/include/fc/io/json.hpp b/include/fc/io/json.hpp index 8a46d134f..21d07c457 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; @@ -18,62 +20,69 @@ namespace fc enum parse_type { legacy_parser = 0, +#ifdef WITH_EXOTIC_JSON_PARSERS strict_parser = 1, relaxed_parser = 2, - legacy_parser_with_string_doubles = 3 + legacy_parser_with_string_doubles = 3, +#endif + broken_nul_parser = 4 }; enum output_formatting { stringify_large_ints_and_doubles = 0, +#ifdef WITH_EXOTIC_JSON_PARSERS legacy_generator = 1 +#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 e4876f259..555b21a86 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 ); + variant variant_from_stream( T& in, uint32_t max_depth ); template fc::string tokenFromStream( T& in ) @@ -104,8 +104,15 @@ namespace fc { namespace json_relaxed if( in.peek() == q ) { in.get(); - if( in.peek() != q ) - return fc::string(); + try + { + if( in.peek() != q ) + return fc::string(); + } + catch( const fc::eof_exception& e ) + { + return fc::string(); + } // triple quote processing if( strict ) @@ -562,86 +569,18 @@ namespace fc { namespace json_relaxed } FC_CAPTURE_AND_RETHROW( (token) ) } template - variant_object objectFromStream( T& in ) + variant_object objectFromStream( T& in, uint32_t max_depth ) { - mutable_variant_object obj; - try - { - char c = in.peek(); - if( c != '{' ) - FC_THROW_EXCEPTION( parse_error_exception, - "Expected '{', but read '${char}'", - ("char",string(&c, &c + 1)) ); - in.get(); - skip_white_space(in); - while( in.peek() != '}' ) - { - if( in.peek() == ',' ) - { - in.get(); - continue; - } - if( skip_white_space(in) ) continue; - string key = json_relaxed::stringFromStream( in ); - skip_white_space(in); - if( in.peek() != ':' ) - { - FC_THROW_EXCEPTION( parse_error_exception, "Expected ':' after key \"${key}\"", - ("key", key) ); - } - in.get(); - auto val = json_relaxed::variant_from_stream( in ); - - obj(std::move(key),std::move(val)); - skip_white_space(in); - } - if( in.peek() == '}' ) - { - in.get(); - return obj; - } - FC_THROW_EXCEPTION( parse_error_exception, "Expected '}' after ${variant}", ("variant", obj ) ); - } - catch( const fc::eof_exception& e ) - { - FC_THROW_EXCEPTION( parse_error_exception, "Unexpected EOF: ${e}", ("e", e.to_detail_string() ) ); - } - catch( const std::ios_base::failure& e ) - { - FC_THROW_EXCEPTION( parse_error_exception, "Unexpected EOF: ${e}", ("e", e.what() ) ); - } FC_RETHROW_EXCEPTIONS( warn, "Error parsing object" ); + std::function get_key = []( T& in ){ return json_relaxed::stringFromStream( in ); }; + 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 ) + variants arrayFromStream( T& in, uint32_t max_depth ) { - variants ar; - try - { - if( in.peek() != '[' ) - FC_THROW_EXCEPTION( parse_error_exception, "Expected '['" ); - in.get(); - skip_white_space(in); - - while( in.peek() != ']' ) - { - if( in.peek() == ',' ) - { - in.get(); - continue; - } - if( skip_white_space(in) ) continue; - ar.push_back( json_relaxed::variant_from_stream(in) ); - skip_white_space(in); - } - if( in.peek() != ']' ) - FC_THROW_EXCEPTION( parse_error_exception, "Expected ']' after parsing ${variant}", - ("variant", ar) ); - - in.get(); - } FC_RETHROW_EXCEPTIONS( warn, "Attempting to parse array ${array}", - ("array", ar ) ); - return ar; + std::function get_value = [max_depth]( T& in ){ return json_relaxed::variant_from_stream( in, max_depth ); }; + return arrayFromStreamBase( in, get_value ); } template @@ -686,60 +625,53 @@ namespace fc { namespace json_relaxed } template - variant variant_from_stream( T& in ) + variant variant_from_stream( T& in, uint32_t max_depth ) { + if( max_depth == 0 ) + FC_THROW_EXCEPTION( parse_error_exception, "Too many nested items in JSON input!" ); skip_white_space(in); - variant var; - while( signed char c = in.peek() ) + signed char c = in.peek(); + switch( c ) { - switch( c ) - { - case ' ': - case '\t': - case '\n': - case '\r': - in.get(); - continue; - case '"': - return json_relaxed::stringFromStream( in ); - case '{': - return json_relaxed::objectFromStream( in ); - case '[': - return json_relaxed::arrayFromStream( in ); - case '-': - case '+': - case '.': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return json_relaxed::numberFromStream( in ); - // null, true, false, or 'warning' / string - case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': - case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': - case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': - case 'y': case 'z': - case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': - case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': - case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': - case 'Y': case 'Z': - case '_': case '/': - return json_relaxed::wordFromStream( in ); - case 0x04: // ^D end of transmission - case EOF: - FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" ); - default: - FC_THROW_EXCEPTION( parse_error_exception, "Unexpected char '${c}' in \"${s}\"", - ("c", c)("s", stringFromToken(in)) ); - } + case '"': + return json_relaxed::stringFromStream( in ); + case '{': + return json_relaxed::objectFromStream( in, max_depth - 1 ); + case '[': + return json_relaxed::arrayFromStream( in, max_depth - 1 ); + case '-': + case '+': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return json_relaxed::numberFromStream( in ); + // null, true, false, or 'warning' / string + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': case '/': + return json_relaxed::wordFromStream( in ); + case 0x04: // ^D end of transmission + case EOF: + FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" ); + case 0: + default: + FC_THROW_EXCEPTION( parse_error_exception, "Unexpected char '${c}' in \"${s}\"", + ("c", c)("s", stringFromToken(in)) ); } - return variant(); } } } // fc::json_relaxed diff --git a/include/fc/smart_ref_impl.hpp b/include/fc/smart_ref_impl.hpp index 7c31ceaa3..fd696941b 100644 --- a/include/fc/smart_ref_impl.hpp +++ b/include/fc/smart_ref_impl.hpp @@ -89,7 +89,7 @@ namespace fc { template T& smart_ref::operator = ( smart_ref&& u ) { if( &u == this ) return *impl; - if( impl ) delete impl; + delete impl; impl = u.impl; u.impl = nullptr; return *impl; diff --git a/src/exception.cpp b/src/exception.cpp index 0e0b9562a..e173be4f3 100644 --- a/src/exception.cpp +++ b/src/exception.cpp @@ -162,7 +162,14 @@ namespace fc for( auto itr = my->_elog.begin(); itr != my->_elog.end(); ) { ss << itr->get_message() <<"\n"; //fc::format_string( itr->get_format(), itr->get_data() ) <<"\n"; - ss << " " << json::to_string( itr->get_data() )<<"\n"; + try + { + ss << " " << json::to_string( itr->get_data() )<<"\n"; + } + catch( const fc::assert_exception& e ) + { + ss << "ERROR: Failed to convert log data to string!\n"; + } ss << " " << itr->get_context().to_string(); ++itr; if( itr != my->_elog.end() ) ss<<"\n"; @@ -256,10 +263,16 @@ namespace fc ("source_lineno", lineno) ("expr", expr) ; - std::cout - << "FC_ASSERT triggered: " - << fc::json::to_string( assert_trip_info ) << "\n"; - return; + try + { + std::cout + << "FC_ASSERT triggered: " + << fc::json::to_string( assert_trip_info ) << "\n"; + } + catch( const fc::assert_exception& e ) + { // this should never happen. assert_trip_info is flat. + std::cout << "ERROR: Failed to convert info to string?!\n"; + } } bool enable_record_assert_trip = false; diff --git a/src/io/json.cpp b/src/io/json.cpp index a53b3a572..b9afc756a 100644 --- a/src/io/json.cpp +++ b/src/io/json.cpp @@ -5,7 +5,7 @@ #include #include #include -//#include +#include #include #include #include @@ -15,22 +15,30 @@ namespace fc { // forward declarations of provided functions - template variant variant_from_stream( T& in ); + 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 objectFromStream( T& in ); - template variants arrayFromStream( T& in ); + template variant_object objectFromStreamBase( T& in, std::function& get_key, std::function& get_value ); + 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 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 ); } +#if __cplusplus > 201402L +#define FALLTHROUGH [[fallthrough]]; +#else +#define FALLTHROUGH +#endif + #include namespace fc @@ -167,8 +175,8 @@ namespace fc ("token", token.str() ) ); } - template - variant_object objectFromStream( T& in ) + template + variant_object objectFromStreamBase( T& in, std::function& get_key, std::function& get_value ) { mutable_variant_object obj; try @@ -179,7 +187,6 @@ namespace fc "Expected '{', but read '${char}'", ("char",string(&c, &c + 1)) ); in.get(); - skip_white_space(in); while( in.peek() != '}' ) { if( in.peek() == ',' ) @@ -188,7 +195,7 @@ namespace fc continue; } if( skip_white_space(in) ) continue; - string key = stringFromStream( in ); + string key = get_key( in ); skip_white_space(in); if( in.peek() != ':' ) { @@ -196,10 +203,9 @@ namespace fc ("key", key) ); } in.get(); - auto val = variant_from_stream( in ); + auto val = get_value( in ); obj(std::move(key),std::move(val)); - skip_white_space(in); } if( in.peek() == '}' ) { @@ -219,7 +225,15 @@ namespace fc } template - variants arrayFromStream( T& in ) + variant_object objectFromStream( T& in, uint32_t max_depth ) + { + std::function get_key = []( T& in ){ return stringFromStream( in ); }; + std::function get_value = [max_depth]( T& in ){ return variant_from_stream( in, max_depth ); }; + return objectFromStreamBase( in, get_key, get_value ); + } + + template + variants arrayFromStreamBase( T& in, std::function& get_value ) { variants ar; try @@ -227,7 +241,6 @@ namespace fc if( in.peek() != '[' ) FC_THROW_EXCEPTION( parse_error_exception, "Expected '['" ); in.get(); - skip_white_space(in); while( in.peek() != ']' ) { @@ -237,8 +250,7 @@ namespace fc continue; } if( skip_white_space(in) ) continue; - ar.push_back( variant_from_stream(in) ); - skip_white_space(in); + ar.push_back( get_value(in) ); } if( in.peek() != ']' ) FC_THROW_EXCEPTION( parse_error_exception, "Expected ']' after parsing ${variant}", @@ -250,6 +262,13 @@ namespace fc return ar; } + template + variants arrayFromStream( T& in, uint32_t max_depth ) + { + std::function get_value = [max_depth]( T& in ){ return variant_from_stream( in, max_depth ); }; + return arrayFromStreamBase( in, get_value ); + } + template variant number_from_stream( T& in ) { @@ -276,6 +295,7 @@ namespace fc if (dot) FC_THROW_EXCEPTION(parse_error_exception, "Can't parse a number with two decimal places"); dot = true; + FALLTHROUGH case '0': case '1': case '2': @@ -299,16 +319,20 @@ namespace fc } } catch (fc::eof_exception&) - { + { // EOF ends the loop } catch (const std::ios_base::failure&) - { + { // read error ends the loop } fc::string str = ss.str(); - if (str == "-." || str == ".") // check the obviously wrong things we could have encountered + if (str == "-." || str == "." || str == "-") // check the obviously wrong things we could have encountered FC_THROW_EXCEPTION(parse_error_exception, "Can't parse token \"${token}\" as a JSON numeric constant", ("token", str)); if( dot ) - return parser_type == json::legacy_parser_with_string_doubles ? variant(str) : variant(to_double(str)); + return +#ifdef WITH_EXOTIC_JSON_PARSERS + parser_type == json::legacy_parser_with_string_doubles ? variant(str) : +#endif + variant(to_double(str)); if( neg ) return to_int64(str); return to_uint64(str); @@ -379,132 +403,77 @@ namespace fc // make out ("falfe") // A strict JSON parser would signal this as an error, but we // will just treat the malformed token as an un-quoted string. - return str + stringFromToken(in);; + return str + stringFromToken(in); } } } template - variant variant_from_stream( T& in ) + variant variant_from_stream( T& in, uint32_t max_depth ) { + if( max_depth == 0 ) + FC_THROW_EXCEPTION( parse_error_exception, "Too many nested items in JSON input!" ); skip_white_space(in); - variant var; - while( signed char c = in.peek() ) + signed char c = in.peek(); + switch( c ) { - switch( c ) - { - case ' ': - case '\t': - case '\n': - case '\r': - in.get(); - continue; - case '"': - return stringFromStream( in ); - case '{': - return objectFromStream( in ); - case '[': - return arrayFromStream( in ); - case '-': - case '.': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return number_from_stream( in ); - // null, true, false, or 'warning' / string - case 'n': - case 't': - case 'f': - return token_from_stream( in ); - case 0x04: // ^D end of transmission - case EOF: - case 0: - FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" ); - default: - FC_THROW_EXCEPTION( parse_error_exception, "Unexpected char '${c}' in \"${s}\"", - ("c", c)("s", stringFromToken(in)) ); - } + case '"': + return stringFromStream( in ); + case '{': + return objectFromStream( in, max_depth - 1 ); + case '[': + return arrayFromStream( in, max_depth - 1 ); + case '-': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return number_from_stream( in ); + // null, true, false, or 'warning' / string + case 'n': + case 't': + case 'f': + return token_from_stream( in ); + case 0x04: // ^D end of transmission + case EOF: + FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" ); + case 0: + if( parser_type == fc::json::broken_nul_parser ) + return variant(); + FALLTHROUGH + default: + FC_THROW_EXCEPTION( parse_error_exception, "Unexpected char '${c}' in \"${s}\"", + ("c", c)("s", stringFromToken(in)) ); } - return variant(); - } + } - - /** the purpose of this check is to verify that we will not get a stack overflow in the recursive descent parser */ - void check_string_depth( const string& utf8_str ) - { - int32_t open_object = 0; - int32_t open_array = 0; - for( auto c : utf8_str ) - { - switch( c ) - { - case '{': open_object++; break; - case '}': open_object--; break; - case '[': open_array++; break; - case ']': open_array--; break; - default: break; - } - FC_ASSERT( open_object < 100 && open_array < 100, "object graph too deep", ("object depth",open_object)("array depth", open_array) ); - } - } - - 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 { - check_string_depth( utf8_str ); - - fc::stringstream in( utf8_str ); - //in.exceptions( std::ifstream::eofbit ); - switch( ptype ) - { - case legacy_parser: - return variant_from_stream( in ); - case legacy_parser_with_string_doubles: - return variant_from_stream( in ); - case strict_parser: - return json_relaxed::variant_from_stream( in ); - case relaxed_parser: - return json_relaxed::variant_from_stream( in ); - default: - FC_ASSERT( false, "Unknown JSON parser type {ptype}", ("ptype", ptype) ); - } + fc::istream_ptr in( new fc::stringstream( utf8_str ) ); + fc::buffered_istream bin( in ); + 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 ) - { try { - check_string_depth( utf8_str ); + variants json::variants_from_string( const std::string& utf8_str, parse_type ptype, uint32_t max_depth ) + { variants result; - fc::stringstream in( utf8_str ); - //in.exceptions( std::ifstream::eofbit ); try { + fc::stringstream in( utf8_str ); while( true ) - { - // result.push_back( variant_from_stream( in )); - result.push_back(json_relaxed::variant_from_stream( in )); - } - } catch ( const fc::eof_exception& ){} - return result; - } FC_RETHROW_EXCEPTIONS( warn, "", ("str",utf8_str) ) } - /* - void toUTF8( const char str, ostream& os ) - { - // validate str == valid utf8 - utf8::replace_invalid( &str, &str + 1, ostream_iterator(os) ); + 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) ) } - void toUTF8( const wchar_t c, ostream& os ) - { - utf8::utf16to8( &c, (&c)+1, ostream_iterator(os) ); - } - */ - /** * Convert '\t', '\a', '\n', '\\' and '"' to "\t\a\n\\\"" * @@ -574,7 +543,6 @@ namespace fc default: os << *itr; - //toUTF8( *itr, os ); } } os << '"'; @@ -586,14 +554,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 << ','; @@ -601,7 +569,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(); @@ -610,7 +578,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 << ','; @@ -619,35 +587,28 @@ 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: os << "null"; return; case variant::int64_type: - { - int64_t i = v.as_int64(); if( format == json::stringify_large_ints_and_doubles && - i > 0xffffffff ) + ( v.as_int64() > INT32_MAX || v.as_int64() < INT32_MIN ) ) os << '"'< 0xffffffff ) + v.as_uint64() > 0xffffffff ) os << '"'<( p, ifstream::binary ); - //auto tmp = std::make_shared( p.generic_string().c_str(), std::ios::binary ); - //buffered_istream bi( tmp ); - boost::filesystem::ifstream bi( p, std::ios::binary ); - switch( ptype ) - { - case legacy_parser: - return variant_from_stream( bi ); - case legacy_parser_with_string_doubles: - return variant_from_stream( bi ); - case strict_parser: - return json_relaxed::variant_from_stream( bi ); - case relaxed_parser: - return json_relaxed::variant_from_stream( bi ); - default: - FC_ASSERT( false, "Unknown JSON parser type {ptype}", ("ptype", ptype) ); - } + fc::istream_ptr in( new fc::ifstream( p ) ); + fc::buffered_istream bin( in ); + 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, 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::stringstream in( utf8_str ); - switch( ptype ) - { - case legacy_parser: - variant_from_stream( in ); - break; - case legacy_parser_with_string_doubles: - variant_from_stream( in ); - break; - case strict_parser: - json_relaxed::variant_from_stream( in ); - break; - case relaxed_parser: - json_relaxed::variant_from_stream( in ); - break; - default: - FC_ASSERT( false, "Unknown JSON parser type {ptype}", ("ptype", ptype) ); - } - try { in.peek(); } catch ( const eof_exception& e ) { return true; } + fc::istream_ptr in( new fc::stringstream( utf8_str ) ); + fc::buffered_istream bin( in ); + from_stream( bin, ptype, max_depth ); + try { bin.peek(); } catch ( const eof_exception& e ) { return true; } return false; } diff --git a/src/log/gelf_appender.cpp b/src/log/gelf_appender.cpp index b88387c69..2f14d78be 100644 --- a/src/log/gelf_appender.cpp +++ b/src/log/gelf_appender.cpp @@ -128,8 +128,15 @@ namespace fc if (!context.get_task_name().empty()) gelf_message["_task_name"] = context.get_task_name(); - string gelf_message_as_string = json::to_string(gelf_message); - //unsigned uncompressed_size = gelf_message_as_string.size(); + string gelf_message_as_string; + try + { + gelf_message_as_string = json::to_string(gelf_message); + } + catch( const fc::assert_exception& e ) + { + gelf_message_as_string = "{\"level\":3,\"short_message\":\"ERROR while generating log message\"}"; + } gelf_message_as_string = zlib_compress(gelf_message_as_string); // graylog2 expects the zlib header to be 0x78 0x9c diff --git a/src/string.cpp b/src/string.cpp index 84edb8884..e04f43961 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include @@ -256,6 +258,60 @@ namespace fc { } } + string format_string( const string& format, const variant_object& args ) + { + stringstream ss; + size_t prev = 0; + auto next = format.find( '$' ); + while( prev < format.size() ) + { + ss << format.substr( prev, next == string::npos ? string::npos : next - prev ); + + // if we got to the end, return it. + if( next == size_t(string::npos) || next == format.size() ) + return ss.str(); + + // if we are not at the end, then update the start + prev = next + 1; + + if( format[prev] == '{' ) + { + // if the next char is a open, then find close + next = format.find( '}', prev ); + // if we found close... + if( next != string::npos ) + { + // the key is between prev and next + string key = format.substr( prev+1, (next-prev-1) ); + + auto val = args.find( key ); + if( val != args.end() ) + { + if( val->value().is_object() || val->value().is_array() ) + { + try + { + ss << json::to_string( val->value() ); + } + catch( const fc::assert_exception& e ) + { + ss << "[\"ERROR_WHILE_CONVERTING_VALUE_TO_STRING\"]"; + } + } + else + ss << val->value().as_string(); + } + else + ss << "${"<& vo ) // vo = std::vector( b64.c_str(), b64.c_str() + b64.size() ); } -string format_string( const string& format, const variant_object& args ) -{ - stringstream ss; - size_t prev = 0; - auto next = format.find( '$' ); - while( prev != size_t(string::npos) && prev < size_t(format.size()) ) - { - ss << format.substr( prev, size_t(next-prev) ); - - // if we got to the end, return it. - if( next == size_t(string::npos) ) - return ss.str(); - - // if we are not at the end, then update the start - prev = next + 1; - - if( format[prev] == '{' ) - { - // if the next char is a open, then find close - next = format.find( '}', prev ); - // if we found close... - if( next != size_t(string::npos) ) - { - // the key is between prev and next - string key = format.substr( prev+1, (next-prev-1) ); - - auto val = args.find( key ); - if( val != args.end() ) - { - if( val->value().is_object() || val->value().is_array() ) - { - ss << json::to_string( val->value() ); - } - else - { - ss << val->value().as_string(); - } - } - else - { - ss << "${"< + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +BOOST_AUTO_TEST_SUITE(json_tests) + +static void replace_some( std::string& str ) +{ + for( size_t i = 0; i < str.length(); i++ ) + if( str[i] == '\1' ) str[i] = '\0'; + else if( str[i] == '\'' ) str[i] = '"'; +} + +static void test_fail_string( const std::string& str ) +{ + try { + fc::json::from_string( str ); + BOOST_FAIL( "json::from_string('" + str + "') failed" ); + } catch( const fc::parse_error_exception& ) { // ignore, ok + } catch( const fc::eof_exception& ) { // ignore, ok + } FC_CAPTURE_LOG_AND_RETHROW( ("json::from_string failed")(str) ) +} + +static void test_fail_stream( const std::string& str ) +{ + fc::temp_file file( fc::temp_directory_path(), true ); + { + std::fstream init( file.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc ); + init.write( str.c_str(), str.length() ); + init.close(); + } + try { + fc::istream_ptr in( new fc::ifstream( file.path() ) ); + fc::buffered_istream bin( in ); + fc::json::from_stream( bin ); + BOOST_FAIL( "json::from_stream('" + str + "') failed using ifstream" ); + } catch( const fc::parse_error_exception& ) { // ignore, ok + } catch( const fc::eof_exception& ) { // ignore, ok + } FC_CAPTURE_LOG_AND_RETHROW( ("json::from_stream failed using ifstream")(str) ) + try { + fc::istream_ptr in( new fc::stringstream( str ) ); + fc::buffered_istream bin( in ); + fc::json::from_stream( bin ); + BOOST_FAIL( "json::from_stream('" + str + "') failed using stringstream" ); + } catch( const fc::parse_error_exception& ) { // ignore, ok + } catch( const fc::eof_exception& ) { // ignore, ok + } FC_CAPTURE_LOG_AND_RETHROW( ("json::from_stream failed using stringstream")(str) ) +} + +static void test_fail_file( const std::string& str ) +{ + fc::temp_file file( fc::temp_directory_path(), true ); + { + std::fstream init( file.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc ); + init.write( str.c_str(), str.length() ); + init.close(); + } + try { + fc::json::from_file( file.path() ); + BOOST_FAIL( "json::from_file('" + str + "') failed using" ); + } catch( const fc::parse_error_exception& ) { // ignore, ok + } catch( const fc::eof_exception& ) { // ignore, ok + } FC_CAPTURE_LOG_AND_RETHROW( ("json::from_file failed")(str) ) +} + +BOOST_AUTO_TEST_CASE(imbalanced_test) +{ + std::string open40("[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["); + std::string close40("]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"); + std::string open80 = open40 + open40; + std::string close80 = close40 + close40; + std::vector tests + { // for easier handling and better readability, in the following test + // strings ' is used instead of " and \1 instead of \0 + "", + "{", + "{'", + "{'}", + "{'a'", + "{'a':", + "{'a':5", + "[", + "['", + "[']", + "[ 13", + "' end", + "{ 13: }", + "\1", + "{\1", + "{\1}", + "{'\1", + "{'\1}", + "{'a'\1", + "{'a'\1}", + "{'a': \1", + "{'a': \1}", + "[\1", + "[\1]", + "['\1", + "['\1]", + "[ 13\1", + "[ 13\1]", + "' end\1", + open80 + "'" + close80 + close80 + "'," + open80 + open80 + + close80 + close80 + close80 + }; + + for( std::string test : tests ) + { + replace_some( test ); + test_fail_string( test ); + test_fail_stream( test ); + test_fail_file( test ); + } +} + +static bool equal( const fc::variant& a, const fc::variant& b ) +{ + auto a_type = a.get_type(); + auto b_type = b.get_type(); + if( a_type == fc::variant::type_id::int64_type && a.as() > 0 ) + a_type = fc::variant::type_id::uint64_type; + if( b_type == fc::variant::type_id::int64_type && b.as() > 0 ) + b_type = fc::variant::type_id::uint64_type; + if( a_type != b_type ) + { + if( ( a_type == fc::variant::type_id::double_type + && b_type == fc::variant::type_id::string_type ) + || ( a_type == fc::variant::type_id::string_type + && b_type == fc::variant::type_id::double_type ) ) + return a.as() == b.as(); + return false; + } + switch( a_type ) + { + case fc::variant::type_id::null_type: return true; + case fc::variant::type_id::int64_type: return a.as() == b.as(); + case fc::variant::type_id::uint64_type: return a.as() == b.as(); + case fc::variant::type_id::double_type: return a.as() == b.as(); + case fc::variant::type_id::bool_type: return a.as() == b.as(); + case fc::variant::type_id::string_type: return a.as() == b.as(); + case fc::variant::type_id::array_type: + if( a.get_array().size() != b.get_array().size() ) return false; + else + { + std::vector::const_iterator b_it = b.get_array().begin(); + for( const auto& a_it : a.get_array() ) + { + if( !equal( a_it, *b_it ) ) return false; + b_it++; + } + } + return true; + case fc::variant::type_id::object_type: + if( a.get_object().size() != b.get_object().size() ) return false; + for( const auto& a_it : a.get_object() ) + { + const auto& b_obj = b.get_object().find( a_it.key() ); + if( b_obj == b.get_object().end() || !equal( a_it.value(), b_obj->value() ) ) return false; + } + return true; + case fc::variant::type_id::blob_type: + default: + FC_THROW_EXCEPTION( fc::invalid_arg_exception, "Unsupported variant type: " + a.get_type() ); + } +} + +static void test_recursive( const fc::variant& v ) +{ try { + const std::string json = fc::json::to_string( v ); + BOOST_CHECK( fc::json::is_valid( json ) ); + BOOST_CHECK( !fc::json::is_valid( json + " " ) ); + + const std::string pretty = fc::json::to_pretty_string( v ); + BOOST_CHECK( fc::json::is_valid( pretty ) ); + BOOST_CHECK( !fc::json::is_valid( pretty + " " ) ); + + fc::temp_file file( fc::temp_directory_path(), true ); + fc::json::save_to_file( v, file.path(), false ); + + fc::variants list = fc::json::variants_from_string( json ); + BOOST_CHECK_EQUAL( 1, list.size() ); + BOOST_CHECK( equal( v, list[0] ) ); + + list = fc::json::variants_from_string( pretty ); + BOOST_CHECK_EQUAL( 1, list.size() ); + BOOST_CHECK( equal( v, list[0] ) ); + + BOOST_CHECK( equal( v, fc::json::from_string( json + " " ) ) ); + BOOST_CHECK( equal( v, fc::json::from_string( pretty + " " ) ) ); + BOOST_CHECK( equal( v, fc::json::from_file( file.path() ) ) ); + + if( v.get_type() == fc::variant::type_id::array_type ) + for( const auto& item : v.get_array() ) + test_recursive( item ); + else if( v.get_type() == fc::variant::type_id::object_type ) + for( const auto& item : v.get_object() ) + test_recursive( item.value() ); +} FC_CAPTURE_LOG_AND_RETHROW( (v) ) } + +BOOST_AUTO_TEST_CASE(structured_test) +{ + fc::variant_object v_empty_obj; + fc::variants v_empty_array; + fc::variant v_null; + fc::variant v_true( true ); + fc::variant v_false( false ); + fc::variant v_empty_str( "" ); + fc::variant v_str( "false" ); + fc::variant v_int8_1( (int8_t) 1 ); + fc::variant v_int8_2( (int8_t) -2 ); + fc::variant v_uint8_1( (int8_t) 1 ); + fc::variant v_int16_1( (int16_t) 1 ); + fc::variant v_int16_2( (int16_t) -2 ); + fc::variant v_uint16_1( (int16_t) 1 ); + fc::variant v_int32_1( (int32_t) 1 ); + fc::variant v_int32_2( (int32_t) -2 ); + fc::variant v_uint32_1( (int32_t) 1 ); + fc::variant v_int64_1( (int8_t) 1 ); + fc::variant v_int64_2( (int8_t) -2 ); + fc::variant v_uint64_1( (int8_t) 1 ); + fc::variant v_float_1( 0.0f ); + fc::variant v_float_2( -2.0f ); + fc::variant v_double_1( 0.0d ); + fc::variant v_double_2( -2.0d ); + fc::variants v_small_array + { + v_empty_obj, + v_empty_array, + v_null, + v_true, + v_false, + v_empty_str + }; + fc::mutable_variant_object v_small_obj; + v_small_obj( "", v_empty_str ) + ( "1", v_empty_array ) + ( "2", v_null ) + ( "a", v_true ) + ( "b", v_false ) + ( "x", v_small_array ) + ( "y", v_empty_obj ); + fc::variants v_big_array + { + v_empty_obj, + v_empty_array, + v_null, + v_true, + v_false, + v_empty_str, + v_str, + v_int8_1, + v_int8_2, + v_uint8_1, + v_int16_1, + v_int16_2, + v_uint16_1, + v_int32_1, + v_int32_2, + v_uint32_1, + v_int64_1, + v_int64_2, + v_uint64_1, + v_float_1, + v_float_2, + v_double_1, + v_double_2, + v_small_array, + v_small_obj + }; + fc::mutable_variant_object v_big_obj; + v_big_obj( "v_empty_obj", v_empty_obj ) + ( "v_empty_array", v_empty_array ) + ( "v_null", v_null ) + ( "v_true", v_true ) + ( "v_false", v_false ) + ( "v_empty_str", v_empty_str ) + ( "v_str", v_str ) + ( "v_int8_1", v_int8_1 ) + ( "v_int8_2", v_int8_2 ) + ( "v_uint8_1", v_uint8_1 ) + ( "v_int16_1", v_int16_1 ) + ( "v_int16_2", v_int16_2 ) + ( "v_uint16_1", v_uint16_1 ) + ( "v_int32_1", v_int32_1 ) + ( "v_int32_2", v_int32_2 ) + ( "v_uint32_1", v_uint32_1 ) + ( "v_int64_1", v_int64_1 ) + ( "v_int64_2", v_int64_2 ) + ( "v_uint64_1", v_uint64_1 ) + ( "v_float_1", v_float_1 ) + ( "v_float_2", v_float_2 ) + ( "v_double_1", v_double_1 ) + ( "v_double_2", v_double_2 ) + ( "v_small_array", v_small_array ) + ( "v_small_obj", v_small_obj ); + v_big_array.push_back( v_big_obj ); + + test_recursive( v_big_array ); +} + +BOOST_AUTO_TEST_CASE(precision_test) +{ + BOOST_CHECK_EQUAL( "\"4294967296\"", fc::json::to_string( fc::variant( 0x100000000LL ) ) ); + BOOST_CHECK_EQUAL( "\"-4294967296\"", fc::json::to_string( fc::variant( -0x100000000LL ) ) ); + std::string half = fc::json::to_string( fc::variant( 0.5 ) ); + BOOST_CHECK_EQUAL( '"', half.front() ); + BOOST_CHECK_EQUAL( '"', half.back() ); + half = half.substr( 1, half.length() - 2 ); + while( '0' == half.back() ) half.erase( half.length() - 1, 1 ); + 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_CASE(rethrow_test) +{ + fc::variants biggie; + for( int i = 0; i < 250; i++ ) + { + fc::variant tmp( std::move(biggie) ); + biggie.reserve(1); + biggie.push_back( std::move(tmp) ); + } + + auto test_r = [&biggie](){ + try { + FC_THROW_EXCEPTION( fc::unknown_host_exception, "WTF?" ); + } FC_RETHROW_EXCEPTIONS( warn, "Argh! ${biggie}", ("biggie",biggie) ) }; + BOOST_CHECK_THROW( test_r(), fc::unknown_host_exception ); + + auto test_lr = [&biggie](){ + try { + FC_THROW_EXCEPTION( fc::unknown_host_exception, "WTF?" ); + } FC_LOG_AND_RETHROW() }; + BOOST_CHECK_THROW( test_lr(), fc::unknown_host_exception ); + + auto test_clr = [&biggie](){ + try { + FC_THROW_EXCEPTION( fc::unknown_host_exception, "WTF?" ); + } FC_CAPTURE_LOG_AND_RETHROW( (biggie) ) }; + BOOST_CHECK_THROW( test_clr(), fc::unknown_host_exception ); + + auto test_cl = [&biggie](){ + try { + FC_THROW_EXCEPTION( fc::unknown_host_exception, "WTF?" ); + } FC_CAPTURE_AND_LOG( (biggie) ) }; + test_cl(); + + auto test_cr = [&biggie](){ + try { + FC_THROW_EXCEPTION( fc::unknown_host_exception, "WTF?" ); + } FC_CAPTURE_AND_RETHROW( (biggie) ) }; + BOOST_CHECK_THROW( test_cr(), fc::unknown_host_exception ); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/io/stream_tests.cpp b/tests/io/stream_tests.cpp new file mode 100644 index 000000000..038c4198d --- /dev/null +++ b/tests/io/stream_tests.cpp @@ -0,0 +1,178 @@ +#include + +#include +#include +#include +#include +#include + +#include + +BOOST_AUTO_TEST_SUITE(stream_tests) + +BOOST_AUTO_TEST_CASE(stringstream_test) +{ + const fc::string constant( "Hello", 6 ); // includes trailing \0 + fc::string writable( "World" ); + fc::stringstream in1( constant ); + fc::stringstream in2( writable ); + fc::stringstream out; + + std::shared_ptr buf( new char[15], [](char* p){ delete[] p; } ); + *buf = 'w'; + in2.writesome( buf, 1, 0 ); + + BOOST_CHECK_EQUAL( 3, in1.readsome( buf, 3, 0 ) ); + BOOST_CHECK_EQUAL( 3, out.writesome( buf, 3, 0 ) ); + BOOST_CHECK_EQUAL( 'l', in1.peek() ); + BOOST_CHECK_EQUAL( 3, in1.readsome( buf, 4, 0 ) ); + BOOST_CHECK_EQUAL( '\0', (&(*buf))[2] ); + BOOST_CHECK_EQUAL( 2, out.writesome( buf, 2, 0 ) ); + *buf = ' '; + out.writesome( buf, 1, 0 ); + BOOST_CHECK_THROW( in1.readsome( buf, 3, 0 ), fc::eof_exception ); + BOOST_CHECK_EQUAL( 5, in2.readsome( buf, 6, 0 ) ); + BOOST_CHECK_EQUAL( 5, out.writesome( buf, 5, 0 ) ); + BOOST_CHECK_THROW( in2.readsome( buf, 3, 0 ), fc::eof_exception ); + + BOOST_CHECK_EQUAL( "Hello world", out.str() ); + BOOST_CHECK_THROW( in1.peek(), fc::eof_exception ); + BOOST_CHECK( in1.eof() ); + BOOST_CHECK_THROW( in2.readsome( buf, 3, 0 ), fc::eof_exception ); + // BOOST_CHECK( in2.eof() ); // fails, apparently readsome doesn't set eof +} + +BOOST_AUTO_TEST_CASE(buffered_stringstream_test) +{ + const fc::string constant( "Hello", 6 ); // includes trailing \0 + fc::string writable( "World" ); + fc::istream_ptr in1( new fc::stringstream( constant ) ); + std::shared_ptr in2( new fc::stringstream( writable ) ); + std::shared_ptr out1( new fc::stringstream() ); + fc::buffered_istream bin1( in1 ); + fc::buffered_istream bin2( in2 ); + fc::buffered_ostream bout( out1 ); + + std::shared_ptr buf( new char[15], [](char* p){ delete[] p; } ); + *buf = 'w'; + in2->writesome( buf, 1, 0 ); + + BOOST_CHECK_EQUAL( 3, bin1.readsome( buf, 3, 0 ) ); + BOOST_CHECK_EQUAL( 3, bout.writesome( buf, 3, 0 ) ); + BOOST_CHECK_EQUAL( 'l', bin1.peek() ); + BOOST_CHECK_EQUAL( 3, bin1.readsome( buf, 4, 0 ) ); + BOOST_CHECK_EQUAL( '\0', (&(*buf))[2] ); + BOOST_CHECK_EQUAL( 2, bout.writesome( buf, 2, 0 ) ); + *buf = ' '; + bout.writesome( buf, 1, 0 ); + BOOST_CHECK_THROW( bin1.readsome( buf, 3, 0 ), fc::eof_exception ); + BOOST_CHECK_EQUAL( 5, bin2.readsome( buf, 6, 0 ) ); + BOOST_CHECK_EQUAL( 5, bout.writesome( buf, 5, 0 ) ); + BOOST_CHECK_THROW( bin2.readsome( buf, 3, 0 ), fc::eof_exception ); + + bout.flush(); + + BOOST_CHECK_EQUAL( "Hello world", out1->str() ); +} + +BOOST_AUTO_TEST_CASE(fstream_test) +{ + fc::temp_file inf1( fc::temp_directory_path(), true ); + fc::temp_file inf2( fc::temp_directory_path(), true ); + fc::temp_file outf( fc::temp_directory_path(), true ); + + { + std::fstream init( inf1.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc ); + init.write( "Hello", 6 ); // includes trailing \0 + init.close(); + + init.open( inf2.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc ); + init.write( "world", 5 ); + init.close(); + + init.open( outf.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc ); + init.close(); + } + + fc::ifstream in1( inf1.path() ); + fc::ifstream in2( inf2.path() ); + fc::ofstream out( outf.path() ); + + std::shared_ptr buf( new char[15], [](char* p){ delete[] p; } ); + BOOST_CHECK_EQUAL( 3, in1.readsome( buf, 3, 0 ) ); + BOOST_CHECK_EQUAL( 3, out.writesome( buf, 3, 0 ) ); + BOOST_CHECK_EQUAL( 3, in1.readsome( buf, 4, 0 ) ); + BOOST_CHECK_EQUAL( '\0', (&(*buf))[2] ); + BOOST_CHECK_EQUAL( 2, out.writesome( buf, 2, 0 ) ); + *buf = ' '; + out.writesome( buf, 1, 0 ); + BOOST_CHECK_THROW( in1.readsome( buf, 3, 0 ), fc::eof_exception ); + BOOST_CHECK_EQUAL( 5, in2.readsome( buf, 6, 0 ) ); + BOOST_CHECK_EQUAL( 5, out.writesome( buf, 5, 0 ) ); + BOOST_CHECK_THROW( in2.readsome( buf, 3, 0 ), fc::eof_exception ); + + { + out.flush(); + std::fstream test( outf.path().to_native_ansi_path(), std::fstream::in ); + BOOST_CHECK_EQUAL( 11, test.readsome( (&(*buf)), 11 ) ); + BOOST_CHECK_EQUAL( "Hello world", std::string( (&(*buf)), 11 ) ); + BOOST_CHECK_EQUAL( 0, test.readsome( (&(*buf)), 11 ) ); + test.close(); + } + + BOOST_CHECK( in1.eof() ); + BOOST_CHECK( in2.eof() ); +} + +BOOST_AUTO_TEST_CASE(buffered_fstream_test) +{ + fc::temp_file inf1( fc::temp_directory_path(), true ); + fc::temp_file inf2( fc::temp_directory_path(), true ); + fc::temp_file outf( fc::temp_directory_path(), true ); + + { + std::fstream init( inf1.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc ); + init.write( "Hello", 6 ); // includes trailing \0 + init.close(); + + init.open( inf2.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc ); + init.write( "world", 5 ); + init.close(); + + init.open( outf.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc ); + init.close(); + } + + fc::istream_ptr in1( new fc::ifstream( inf1.path() ) ); + fc::istream_ptr in2( new fc::ifstream( inf2.path() ) ); + fc::ostream_ptr out( new fc::ofstream( outf.path() ) ); + fc::buffered_istream bin1( in1 ); + fc::buffered_istream bin2( in2 ); + fc::buffered_ostream bout( out ); + + std::shared_ptr buf( new char[15], [](char* p){ delete[] p; } ); + + BOOST_CHECK_EQUAL( 3, bin1.readsome( buf, 3, 0 ) ); + BOOST_CHECK_EQUAL( 3, bout.writesome( buf, 3, 0 ) ); + BOOST_CHECK_EQUAL( 'l', bin1.peek() ); + BOOST_CHECK_EQUAL( 3, bin1.readsome( buf, 4, 0 ) ); + BOOST_CHECK_EQUAL( '\0', (&(*buf))[2] ); + BOOST_CHECK_EQUAL( 2, bout.writesome( buf, 2, 0 ) ); + *buf = ' '; + bout.writesome( buf, 1, 0 ); + BOOST_CHECK_THROW( bin1.readsome( buf, 3, 0 ), fc::eof_exception ); + BOOST_CHECK_EQUAL( 5, bin2.readsome( buf, 6, 0 ) ); + BOOST_CHECK_EQUAL( 5, bout.writesome( buf, 5, 0 ) ); + BOOST_CHECK_THROW( bin2.readsome( buf, 3, 0 ), fc::eof_exception ); + + { + bout.flush(); + std::fstream test( outf.path().to_native_ansi_path(), std::fstream::in ); + BOOST_CHECK_EQUAL( 11, test.readsome( (&(*buf)), 11 ) ); + BOOST_CHECK_EQUAL( "Hello world", std::string( (&(*buf)), 11 ) ); + BOOST_CHECK_EQUAL( 0, test.readsome( (&(*buf)), 11 ) ); + test.close(); + } +} + +BOOST_AUTO_TEST_SUITE_END()