diff --git a/README.md b/README.md index 380f6c75..3eb17caf 100644 --- a/README.md +++ b/README.md @@ -85,8 +85,8 @@ sudo apt install pkg-config libssl-dev zlib1g-dev libgtest-dev libgmock-dev Install [dconv](https://github.com/joinframework/dconv) from its release package: ```bash -wget https://github.com/joinframework/dconv/releases/download/v1.2.3/libdconv-dev_1.2.3_all.deb -sudo apt install ./libdconv-dev_1.2.3_all.deb +wget https://github.com/joinframework/dconv/releases/download/v2.0.0/libdconv-dev_2.0.0_all.deb +sudo apt install ./libdconv-dev_2.0.0_all.deb ``` > **Compilers:** GCC 11+ or Clang 16+ (C++20). Clang requires `libclang-rt-dev` for coverage instrumentation (`--coverage`). diff --git a/cmake/joinConfig.cmake.in b/cmake/joinConfig.cmake.in index 4f2f8866..ca7695e3 100644 --- a/cmake/joinConfig.cmake.in +++ b/cmake/joinConfig.cmake.in @@ -23,7 +23,7 @@ foreach(comp ${join_FIND_COMPONENTS}) endforeach() if(@JOIN_ENABLE_DATA@ OR @JOIN_ENABLE_SERVICES@) - find_dependency(dconv 1.2.3) + find_dependency(dconv 2.0.0) find_dependency(ZLIB) endif() diff --git a/cmake/packaging.cmake b/cmake/packaging.cmake index 1452d44b..caf22d40 100644 --- a/cmake/packaging.cmake +++ b/cmake/packaging.cmake @@ -32,7 +32,7 @@ if(JOIN_ENABLE_CRYPTO) string(APPEND JOIN_DEV_DEPENDS ", libssl-dev") endif() if(JOIN_ENABLE_DATA) - string(APPEND JOIN_DEV_DEPENDS ", libdconv-dev (>= 1.2.3), zlib1g-dev") + string(APPEND JOIN_DEV_DEPENDS ", libdconv-dev (>= 2.0.0), zlib1g-dev") endif() if(JOIN_ENABLE_IO_URING) string(APPEND JOIN_DEV_DEPENDS ", liburing-dev") diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 62ff6d55..945f5aa5 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.22.1) -find_package(dconv 1.2.3 REQUIRED) +find_package(dconv 2.0.0 REQUIRED) find_package(ZLIB REQUIRED) set(PUBLIC_HEADERS diff --git a/data/include/join/json.hpp b/data/include/join/json.hpp index 110eea4e..17a73cb0 100644 --- a/data/include/join/json.hpp +++ b/data/include/join/json.hpp @@ -190,8 +190,9 @@ namespace join */ enum class JsonWriteMode { - None, /**< no write mode set. */ - Canonical, /**< canonical form. */ + None, /**< no write mode set. */ + ValidateEncoding, /**< validate UTF-8 encoding of strings. */ + Canonical, /**< canonical form. */ }; /** @@ -334,40 +335,16 @@ namespace join */ template int setDouble (double value) - requires (Mode == JsonWriteMode::None) + requires (Mode != JsonWriteMode::Canonical) { - array (); - uint64_t bits; - memcpy (&bits, &value, sizeof (bits)); - const bool neg = (bits >> 63) != 0; - const uint64_t exp = (bits >> 52) & 0x7FFULL; - const uint64_t frac = bits & 0x000FFFFFFFFFFFFFULL; - if (exp != 0x7FFULL) + if (JOIN_UNLIKELY (!std::isfinite (value))) { - writeDouble (value); - } - else if (frac != 0) - { - if (neg) - { - append ("-NaN", 4); - } - else - { - append ("NaN", 3); - } - } - else - { - if (neg) - { - append ("-Inf", 4); - } - else - { - append ("Inf", 3); - } + join::lastError = make_error_code (SaxErrc::InvalidValue); + reset (); + return -1; } + array (); + writeDouble (value); endValue (); return 0; } @@ -381,28 +358,27 @@ namespace join int setDouble (double value) requires (Mode == JsonWriteMode::Canonical) { + if (JOIN_UNLIKELY (!std::isfinite (value))) + { + join::lastError = make_error_code (SaxErrc::InvalidValue); + reset (); + return -1; + } array (); - if (std::isfinite (value)) + if ((std::trunc (value) == value) && (value >= 0) && + (value < static_cast (std::numeric_limits::max ()))) { - if ((std::trunc (value) == value) && (value >= 0) && - (value < static_cast (std::numeric_limits::max ()))) - { - writeUint64 (static_cast (value)); - } - else if ((std::trunc (value) == value) && - (value >= static_cast (std::numeric_limits::min ())) && - (value < static_cast (std::numeric_limits::max ()))) - { - writeInt64 (static_cast (value)); - } - else - { - writeDouble (value); - } + writeUint64 (static_cast (value)); + } + else if ((std::trunc (value) == value) && + (value >= static_cast (std::numeric_limits::min ())) && + (value < static_cast (std::numeric_limits::max ()))) + { + writeInt64 (static_cast (value)); } else { - append ("null", 4); + writeDouble (value); } endValue (); return 0; @@ -415,7 +391,7 @@ namespace join */ int setString (std::string_view value) { - if constexpr (WriteMode == JsonWriteMode::Canonical) + if constexpr (WriteMode != JsonWriteMode::None) { if (JOIN_UNLIKELY (utf8::validate (value.data (), value.data () + value.size ()) == -1)) { @@ -516,7 +492,7 @@ namespace join */ template const View& members (const View& object) const noexcept - requires (Mode == JsonWriteMode::None) + requires (Mode != JsonWriteMode::Canonical) { return object; } @@ -570,7 +546,7 @@ namespace join reset (); return -1; } - if constexpr (WriteMode == JsonWriteMode::Canonical) + if constexpr (WriteMode != JsonWriteMode::None) { if (JOIN_UNLIKELY (utf8::validate (key.data (), key.data () + key.size ()) == -1)) { @@ -1363,7 +1339,8 @@ namespace join bool negative = false; std::error_code ec; - if (JOIN_LIKELY (dconv::atod (document, value, significand, exponent, negative, ec) == 0)) + if (JOIN_LIKELY ( + dconv::atod (document, value, significand, exponent, negative, ec) == 0)) { return handler.setDouble (value); } diff --git a/data/tests/json_canonicalizer_test.cpp b/data/tests/json_canonicalizer_test.cpp index 65430d65..a99f6ff0 100644 --- a/data/tests/json_canonicalizer_test.cpp +++ b/data/tests/json_canonicalizer_test.cpp @@ -110,8 +110,9 @@ TEST (JsonCanonicalizer, nan) std::stringstream out; JsonCanonicalizer writer (out); - ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); - EXPECT_EQ (out.str (), "[null,null]"); + ASSERT_EQ (value.serialize (writer), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidValue); + EXPECT_TRUE (out.str ().empty ()); } /** @@ -125,8 +126,9 @@ TEST (JsonCanonicalizer, infinity) std::stringstream out; JsonCanonicalizer writer (out); - ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); - EXPECT_EQ (out.str (), "[null,null]"); + ASSERT_EQ (value.serialize (writer), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidValue); + EXPECT_TRUE (out.str ().empty ()); } /** diff --git a/data/tests/json_reader_test.cpp b/data/tests/json_reader_test.cpp index 88e90a65..b2f6b05a 100644 --- a/data/tests/json_reader_test.cpp +++ b/data/tests/json_reader_test.cpp @@ -231,51 +231,48 @@ TEST (JsonReader, pass) stream.clear (); stream.str ("[NaN]"); - ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); - ASSERT_TRUE (value.isArray ()); - ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isDouble ()); - ASSERT_TRUE (!std::signbit (value[0].getDouble ()) && std::isnan (value[0].getDouble ())); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidValue); stream.clear (); stream.str ("[-NaN]"); - ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); - ASSERT_TRUE (value.isArray ()); - ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isDouble ()); - ASSERT_TRUE (std::signbit (value[0].getDouble ()) && std::isnan (value[0].getDouble ())); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidValue); stream.clear (); stream.str ("[Inf]"); - ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); - ASSERT_TRUE (value.isArray ()); - ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isDouble ()); - ASSERT_TRUE (!std::signbit (value[0].getDouble ()) && std::isinf (value[0].getDouble ())); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidValue); stream.clear (); stream.str ("[-Inf]"); - ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); - ASSERT_TRUE (value.isArray ()); - ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isDouble ()); - ASSERT_TRUE (std::signbit (value[0].getDouble ()) && std::isinf (value[0].getDouble ())); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidValue); stream.clear (); stream.str ("[Infinity]"); - ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); - ASSERT_TRUE (value.isArray ()); - ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isDouble ()); - ASSERT_TRUE (!std::signbit (value[0].getDouble ()) && std::isinf (value[0].getDouble ())); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidValue); stream.clear (); stream.str ("[-Infinity]"); - ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); - ASSERT_TRUE (value.isArray ()); - ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isDouble ()); - ASSERT_TRUE (std::signbit (value[0].getDouble ()) && std::isinf (value[0].getDouble ())); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidValue); + + stream.clear (); + stream.str ("[01]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidValue); + + stream.clear (); + stream.str ("[1.]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidValue); + + stream.clear (); + stream.str ("[.5]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidValue); stream.clear (); stream.str ("[true]"); diff --git a/data/tests/json_roundtrip_test.cpp b/data/tests/json_roundtrip_test.cpp index 26ac0c54..85d8eac2 100644 --- a/data/tests/json_roundtrip_test.cpp +++ b/data/tests/json_roundtrip_test.cpp @@ -267,35 +267,23 @@ TEST (Json, roundtrip) stream.clear (); stream.str ("[Inf]"); - ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); - stream.str (""); - stream.clear (); - ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); - EXPECT_EQ (stream.str (), "[Inf]"); + ASSERT_EQ (reader.deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidValue); stream.clear (); stream.str ("[-Inf]"); - ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); - stream.str (""); - stream.clear (); - ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); - EXPECT_EQ (stream.str (), "[-Inf]"); + ASSERT_EQ (reader.deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidValue); stream.clear (); stream.str ("[NaN]"); - ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); - stream.str (""); - stream.clear (); - ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); - EXPECT_EQ (stream.str (), "[NaN]"); + ASSERT_EQ (reader.deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidValue); stream.clear (); stream.str ("[-NaN]"); - ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); - stream.str (""); - stream.clear (); - ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); - EXPECT_EQ (stream.str (), "[-NaN]"); + ASSERT_EQ (reader.deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidValue); } /** diff --git a/data/tests/json_writer_test.cpp b/data/tests/json_writer_test.cpp index 94278993..5a2708c7 100644 --- a/data/tests/json_writer_test.cpp +++ b/data/tests/json_writer_test.cpp @@ -190,20 +190,29 @@ TEST (JsonWriter, setDouble) EXPECT_EQ (stream.str (), "1.23456789e-13"); stream.str (""); - EXPECT_EQ (jsonWriter.setDouble (-std::numeric_limits::infinity ()), 0); - EXPECT_EQ (stream.str (), "-Inf"); + EXPECT_EQ (jsonWriter.setDouble (-std::numeric_limits::infinity ()), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidValue); + EXPECT_TRUE (stream.str ().empty ()); + + stream.str (""); + EXPECT_EQ (jsonWriter.setDouble (std::numeric_limits::infinity ()), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidValue); + EXPECT_TRUE (stream.str ().empty ()); stream.str (""); - EXPECT_EQ (jsonWriter.setDouble (std::numeric_limits::infinity ()), 0); - EXPECT_EQ (stream.str (), "Inf"); + EXPECT_EQ (jsonWriter.setDouble (-std::numeric_limits::quiet_NaN ()), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidValue); + EXPECT_TRUE (stream.str ().empty ()); stream.str (""); - EXPECT_EQ (jsonWriter.setDouble (-std::numeric_limits::quiet_NaN ()), 0); - EXPECT_EQ (stream.str (), "-NaN"); + EXPECT_EQ (jsonWriter.setDouble (std::numeric_limits::quiet_NaN ()), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidValue); + EXPECT_TRUE (stream.str ().empty ()); + join::BasicJsonWriter validator (stream); stream.str (""); - EXPECT_EQ (jsonWriter.setDouble (std::numeric_limits::quiet_NaN ()), 0); - EXPECT_EQ (stream.str (), "NaN"); + EXPECT_EQ (validator.setDouble (1.0), 0); + EXPECT_EQ (stream.str (), "1.0"); stream.str (""); std::string expected ("[98.6"); @@ -233,6 +242,21 @@ TEST (JsonWriter, setString) stream.str (""); EXPECT_EQ (jsonWriter.setString ("\"\\\b\f\n\r\t\x19"), 0); EXPECT_EQ (stream.str (), "\"\\\"\\\\\\b\\f\\n\\r\\t\\u0019\""); + + stream.str (""); + EXPECT_EQ (jsonWriter.setString ("\xff"), 0); + EXPECT_EQ (stream.str (), "\"\xff\""); + + join::BasicJsonWriter validator (stream); + + stream.str (""); + EXPECT_EQ (validator.setString ("caf\xc3\xa9"), 0); + EXPECT_EQ (stream.str (), "\"caf\xc3\xa9\""); + + stream.str (""); + EXPECT_EQ (validator.setString ("\xff"), -1); + EXPECT_EQ (join::lastError, join::JsonErrc::InvalidEncoding); + EXPECT_TRUE (stream.str ().empty ()); } /** @@ -400,6 +424,13 @@ TEST (JsonWriter, setKey) EXPECT_EQ (jsonWriter.setKey ("\"\\\b\f\n\r\t\x19"), 0); jsonWriter.flush (); EXPECT_EQ (stream.str (), "{\"\\\"\\\\\\b\\f\\n\\r\\t\\u0019\":"); + + join::BasicJsonWriter validator (stream); + stream.str (""); + EXPECT_EQ (validator.startObject (), 0); + EXPECT_EQ (validator.setKey ("\xff"), -1); + EXPECT_EQ (join::lastError, join::JsonErrc::InvalidEncoding); + EXPECT_TRUE (stream.str ().empty ()); } /**