From 5bccacda3018a944756156f1b9b5930c128fb32f Mon Sep 17 00:00:00 2001
From: Julian Becker <becker.julian@gmail.com>
Date: Tue, 16 Oct 2018 19:13:07 +0200
Subject: [PATCH] BSON: throw json.exception.out_of_range.407 in case a value
 of type `std::uint64_t` is serialized to BSON. Also, added a missing
 EOF-check to binary_reader.

---
 include/nlohmann/detail/exceptions.hpp        |   2 +-
 .../nlohmann/detail/input/binary_reader.hpp   |   5 +
 .../nlohmann/detail/output/binary_writer.hpp  |   7 +-
 single_include/nlohmann/json.hpp              |  14 +-
 test/src/unit-bson.cpp                        | 354 +++++++++++++++++-
 5 files changed, 376 insertions(+), 6 deletions(-)

diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp
index 7edc0032..e23dd0d7 100644
--- a/include/nlohmann/detail/exceptions.hpp
+++ b/include/nlohmann/detail/exceptions.hpp
@@ -264,7 +264,7 @@ json.exception.out_of_range.403 | key 'foo' not found | The provided key was not
 json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved.
 json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value.
 json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF.
-json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON only supports integers numbers up to 9223372036854775807. |
+json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. |
 json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. |
 
 @liveexample{The following code shows how an `out_of_range` exception can be
diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp
index 2b3ff1ab..8dfe3b98 100644
--- a/include/nlohmann/detail/input/binary_reader.hpp
+++ b/include/nlohmann/detail/input/binary_reader.hpp
@@ -256,6 +256,11 @@ class binary_reader
     {
         while (auto element_type = get())
         {
+            if (JSON_UNLIKELY(not unexpect_eof()))
+            {
+                return false;
+            }
+
             const std::size_t element_type_parse_position = chars_read;
             string_t key;
             if (JSON_UNLIKELY(not get_bson_cstr(key)))
diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp
index 0ec237ea..0a7cf45f 100644
--- a/include/nlohmann/detail/output/binary_writer.hpp
+++ b/include/nlohmann/detail/output/binary_writer.hpp
@@ -800,11 +800,16 @@ class binary_writer
             write_bson_entry_header(name, 0x10); // int32
             write_number<std::int32_t, true>(static_cast<std::int32_t>(value));
         }
-        else
+        else if (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
         {
             write_bson_entry_header(name, 0x12); // int64
             write_number<std::int64_t, true>(static_cast<std::int64_t>(value));
         }
+        else
+        {
+            JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(value)));
+        }
+
     }
 
     /*!
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index 98c6b1f1..4bd80c54 100644
--- a/single_include/nlohmann/json.hpp
+++ b/single_include/nlohmann/json.hpp
@@ -863,7 +863,7 @@ json.exception.out_of_range.403 | key 'foo' not found | The provided key was not
 json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved.
 json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value.
 json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF.
-json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON only supports integers numbers up to 9223372036854775807. |
+json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. |
 json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. |
 
 @liveexample{The following code shows how an `out_of_range` exception can be
@@ -6198,6 +6198,11 @@ class binary_reader
     {
         while (auto element_type = get())
         {
+            if (JSON_UNLIKELY(not unexpect_eof()))
+            {
+                return false;
+            }
+
             const std::size_t element_type_parse_position = chars_read;
             string_t key;
             if (JSON_UNLIKELY(not get_bson_cstr(key)))
@@ -8644,11 +8649,16 @@ class binary_writer
             write_bson_entry_header(name, 0x10); // int32
             write_number<std::int32_t, true>(static_cast<std::int32_t>(value));
         }
-        else
+        else if (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
         {
             write_bson_entry_header(name, 0x12); // int64
             write_number<std::int64_t, true>(static_cast<std::int64_t>(value));
         }
+        else
+        {
+            JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(value)));
+        }
+
     }
 
     /*!
diff --git a/test/src/unit-bson.cpp b/test/src/unit-bson.cpp
index 4a1b387e..58ad71bd 100644
--- a/test/src/unit-bson.cpp
+++ b/test/src/unit-bson.cpp
@@ -31,7 +31,6 @@ SOFTWARE.
 
 #include <nlohmann/json.hpp>
 using nlohmann::json;
-
 #include <fstream>
 
 TEST_CASE("BSON")
@@ -708,7 +707,7 @@ TEST_CASE("Incomplete BSON INPUT 3")
         // missing input data...
     };
     CHECK_THROWS_WITH(json::from_bson(incomplete_bson),
-                      "[json.exception.parse_error.110] parse error at 29: unexpected end of input");
+                      "[json.exception.parse_error.110] parse error at 28: unexpected end of input");
     CHECK(json::from_bson(incomplete_bson, true, false).is_discarded());
 
     SaxCountdown scp(1);
@@ -752,3 +751,354 @@ TEST_CASE("Unsupported BSON input")
 }
 
 
+
+TEST_CASE("BSON numerical data")
+{
+    SECTION("number")
+    {
+        SECTION("signed")
+        {
+            SECTION("std::int64_t: INT64_MIN .. INT32_MIN-1")
+            {
+                std::vector<int64_t> numbers
+                {
+                    INT64_MIN,
+                    -1000000000000000000LL,
+                    -100000000000000000LL,
+                    -10000000000000000LL,
+                    -1000000000000000LL,
+                    -100000000000000LL,
+                    -10000000000000LL,
+                    -1000000000000LL,
+                    -100000000000LL,
+                    -10000000000LL,
+                    static_cast<std::int64_t>(INT32_MIN) - 1,
+                };
+
+                for (auto i : numbers)
+                {
+
+                    CAPTURE(i);
+
+                    json j =
+                    {
+                        { "entry", i }
+                    };
+                    CHECK(j.at("entry").is_number_integer());
+
+                    std::uint64_t iu = *reinterpret_cast<std::uint64_t*>(&i);
+                    std::vector<uint8_t> expected_bson =
+                    {
+                        0x14u, 0x00u, 0x00u, 0x00u, // size (little endian)
+                        0x12u, /// entry: int64
+                        'e', 'n', 't', 'r', 'y', '\x00',
+                        static_cast<uint8_t>((iu >> (8u * 0u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 1u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 2u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 3u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 4u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 5u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 6u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 7u)) & 0xffu),
+                        0x00u // end marker
+                    };
+
+                    const auto bson = json::to_bson(j);
+                    CHECK(bson == expected_bson);
+
+                    auto j_roundtrip = json::from_bson(bson);
+
+                    CHECK(j_roundtrip.at("entry").is_number_integer());
+                    CHECK(j_roundtrip == j);
+                    CHECK(json::from_bson(bson, true, false) == j);
+
+                }
+            }
+
+
+            SECTION("signed std::int32_t: INT32_MIN .. INT32_MAX")
+            {
+                std::vector<int32_t> numbers
+                {
+                    INT32_MIN,
+                    -2147483647L,
+                    -1000000000L,
+                    -100000000L,
+                    -10000000L,
+                    -1000000L,
+                    -100000L,
+                    -10000L,
+                    -1000L,
+                    -100L,
+                    -10L,
+                    -1L,
+                    0L,
+                    1L,
+                    10L,
+                    100L,
+                    1000L,
+                    10000L,
+                    100000L,
+                    1000000L,
+                    10000000L,
+                    100000000L,
+                    1000000000L,
+                    2147483646L,
+                    INT32_MAX
+                };
+
+                for (auto i : numbers)
+                {
+
+                    CAPTURE(i);
+
+                    json j =
+                    {
+                        { "entry", i }
+                    };
+                    CHECK(j.at("entry").is_number_integer());
+
+                    std::uint64_t iu = *reinterpret_cast<std::uint64_t*>(&i);
+                    std::vector<uint8_t> expected_bson =
+                    {
+                        0x10u, 0x00u, 0x00u, 0x00u, // size (little endian)
+                        0x10u, /// entry: int32
+                        'e', 'n', 't', 'r', 'y', '\x00',
+                        static_cast<uint8_t>((iu >> (8u * 0u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 1u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 2u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 3u)) & 0xffu),
+                        0x00u // end marker
+                    };
+
+                    const auto bson = json::to_bson(j);
+                    CHECK(bson == expected_bson);
+
+                    auto j_roundtrip = json::from_bson(bson);
+
+                    CHECK(j_roundtrip.at("entry").is_number_integer());
+                    CHECK(j_roundtrip == j);
+                    CHECK(json::from_bson(bson, true, false) == j);
+
+                }
+            }
+
+            SECTION("signed std::int64_t: INT32_MAX+1 .. INT64_MAX")
+            {
+                std::vector<int64_t> numbers
+                {
+                    INT64_MAX,
+                    1000000000000000000LL,
+                    100000000000000000LL,
+                    10000000000000000LL,
+                    1000000000000000LL,
+                    100000000000000LL,
+                    10000000000000LL,
+                    1000000000000LL,
+                    100000000000LL,
+                    10000000000LL,
+                    static_cast<std::int64_t>(INT32_MAX) + 1,
+                };
+
+                for (auto i : numbers)
+                {
+
+                    CAPTURE(i);
+
+                    json j =
+                    {
+                        { "entry", i }
+                    };
+                    CHECK(j.at("entry").is_number_integer());
+
+                    std::uint64_t iu = *reinterpret_cast<std::uint64_t*>(&i);
+                    std::vector<uint8_t> expected_bson =
+                    {
+                        0x14u, 0x00u, 0x00u, 0x00u, // size (little endian)
+                        0x12u, /// entry: int64
+                        'e', 'n', 't', 'r', 'y', '\x00',
+                        static_cast<uint8_t>((iu >> (8u * 0u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 1u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 2u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 3u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 4u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 5u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 6u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 7u)) & 0xffu),
+                        0x00u // end marker
+                    };
+
+                    const auto bson = json::to_bson(j);
+                    CHECK(bson == expected_bson);
+
+                    auto j_roundtrip = json::from_bson(bson);
+
+                    CHECK(j_roundtrip.at("entry").is_number_integer());
+                    CHECK(j_roundtrip == j);
+                    CHECK(json::from_bson(bson, true, false) == j);
+
+                }
+            }
+        }
+
+        SECTION("unsigned")
+        {
+            SECTION("unsigned std::uint64_t: 0 .. INT32_MAX")
+            {
+                std::vector<std::uint64_t> numbers
+                {
+                    0ULL,
+                    1ULL,
+                    10ULL,
+                    100ULL,
+                    1000ULL,
+                    10000ULL,
+                    100000ULL,
+                    1000000ULL,
+                    10000000ULL,
+                    100000000ULL,
+                    1000000000ULL,
+                    2147483646ULL,
+                    static_cast<std::uint64_t>(INT32_MAX)
+                };
+
+                for (auto i : numbers)
+                {
+
+                    CAPTURE(i);
+
+                    json j =
+                    {
+                        { "entry", i }
+                    };
+
+                    std::uint64_t iu = *reinterpret_cast<std::uint64_t*>(&i);
+                    std::vector<uint8_t> expected_bson =
+                    {
+                        0x10u, 0x00u, 0x00u, 0x00u, // size (little endian)
+                        0x10u, /// entry: int32
+                        'e', 'n', 't', 'r', 'y', '\x00',
+                        static_cast<uint8_t>((iu >> (8u * 0u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 1u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 2u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 3u)) & 0xffu),
+                        0x00u // end marker
+                    };
+
+                    const auto bson = json::to_bson(j);
+                    CHECK(bson == expected_bson);
+
+                    auto j_roundtrip = json::from_bson(bson);
+
+                    CHECK(j.at("entry").is_number_unsigned());
+                    CHECK(j_roundtrip.at("entry").is_number_integer());
+                    CHECK(j_roundtrip == j);
+                    CHECK(json::from_bson(bson, true, false) == j);
+
+                }
+            }
+
+            SECTION("unsigned std::uint64_t: INT32_MAX+1 .. INT64_MAX")
+            {
+                std::vector<std::uint64_t> numbers
+                {
+                    static_cast<std::uint64_t>(INT32_MAX) + 1,
+                    4000000000ULL,
+                    static_cast<std::uint64_t>(UINT32_MAX),
+                    10000000000ULL,
+                    100000000000ULL,
+                    1000000000000ULL,
+                    10000000000000ULL,
+                    100000000000000ULL,
+                    1000000000000000ULL,
+                    10000000000000000ULL,
+                    100000000000000000ULL,
+                    1000000000000000000ULL,
+                    static_cast<std::uint64_t>(INT64_MAX),
+                };
+
+                for (auto i : numbers)
+                {
+
+                    CAPTURE(i);
+
+                    json j =
+                    {
+                        { "entry", i }
+                    };
+
+                    std::uint64_t iu = *reinterpret_cast<std::uint64_t*>(&i);
+                    std::vector<uint8_t> expected_bson =
+                    {
+                        0x14u, 0x00u, 0x00u, 0x00u, // size (little endian)
+                        0x12u, /// entry: int64
+                        'e', 'n', 't', 'r', 'y', '\x00',
+                        static_cast<uint8_t>((iu >> (8u * 0u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 1u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 2u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 3u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 4u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 5u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 6u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 7u)) & 0xffu),
+                        0x00u // end marker
+                    };
+
+                    const auto bson = json::to_bson(j);
+                    CHECK(bson == expected_bson);
+
+                    auto j_roundtrip = json::from_bson(bson);
+
+                    CHECK(j.at("entry").is_number_unsigned());
+                    CHECK(j_roundtrip.at("entry").is_number_integer());
+                    CHECK(j_roundtrip == j);
+                    CHECK(json::from_bson(bson, true, false) == j);
+                }
+            }
+
+            SECTION("unsigned std::uint64_t: INT64_MAX+1 .. UINT64_MAX")
+            {
+                std::vector<std::uint64_t> numbers
+                {
+                    static_cast<std::uint64_t>(INT64_MAX) + 1ULL,
+                    10000000000000000000ULL,
+                    18000000000000000000ULL,
+                    UINT64_MAX - 1ULL,
+                    UINT64_MAX,
+                };
+
+                for (auto i : numbers)
+                {
+
+                    CAPTURE(i);
+
+                    json j =
+                    {
+                        { "entry", i }
+                    };
+
+                    std::uint64_t iu = *reinterpret_cast<std::uint64_t*>(&i);
+                    std::vector<uint8_t> expected_bson =
+                    {
+                        0x14u, 0x00u, 0x00u, 0x00u, // size (little endian)
+                        0x12u, /// entry: int64
+                        'e', 'n', 't', 'r', 'y', '\x00',
+                        static_cast<uint8_t>((iu >> (8u * 0u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 1u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 2u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 3u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 4u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 5u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 6u)) & 0xffu),
+                        static_cast<uint8_t>((iu >> (8u * 7u)) & 0xffu),
+                        0x00u // end marker
+                    };
+
+                    CHECK_THROWS_AS(json::to_bson(j), json::out_of_range&);
+                    CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.out_of_range.407] number overflow serializing " + std::to_string(i));
+                }
+            }
+
+        }
+    }
+}