diff --git a/ext/cbson/cbson.c b/ext/cbson/cbson.c index 37708cc..b29c013 100644 --- a/ext/cbson/cbson.c +++ b/ext/cbson/cbson.c @@ -151,6 +151,10 @@ static int write_element_allow_id(VALUE key, VALUE value, VALUE extra, int allow switch(TYPE(value)) { case T_BIGNUM: { + if (rb_funcall(value, rb_intern(">"), 1, INT2NUM(2147483647)) == Qtrue || + rb_funcall(value, rb_intern("<"), 1, INT2NUM(-2147483648)) == Qtrue) { + rb_raise(rb_eRangeError, "MongoDB can only handle 4-byte ints - try converting to a double before saving"); + } write_name_and_type(buffer, key, 0x10); VALUE as_f = rb_funcall(value, rb_intern("to_f"), 0); int int_value = NUM2LL(as_f); diff --git a/lib/mongo/util/bson.rb b/lib/mongo/util/bson.rb index 91d9e07..a4acd43 100644 --- a/lib/mongo/util/bson.rb +++ b/lib/mongo/util/bson.rb @@ -248,7 +248,10 @@ class BSON end def deserialize_number_int_data(buf) - buf.get_int + # sometimes ruby makes me angry... why would the same code pack as signed + # but unpack as unsigned + unsigned = buf.get_int + unsigned >= 2**32 / 2 ? unsigned - 2**32 : unsigned end def deserialize_object_data(buf) @@ -387,6 +390,9 @@ class BSON if type == NUMBER buf.put_double(val) else + if val > 2**32 / 2 - 1 or val < -2**32 / 2 + raise RangeError.new "MongoDB can only handle 4-byte ints - try converting to a double before saving" + end buf.put_int(val) end end diff --git a/tests/test_bson.rb b/tests/test_bson.rb index be67a1b..e2b318c 100644 --- a/tests/test_bson.rb +++ b/tests/test_bson.rb @@ -36,6 +36,18 @@ class BSONTest < Test::Unit::TestCase doc = {'doc' => 42} @b.serialize(doc) assert_equal doc, @b.deserialize + + doc = {"doc" => -5600} + @b.serialize(doc) + assert_equal doc, @b.deserialize + + doc = {"doc" => 2147483647} + @b.serialize(doc) + assert_equal doc, @b.deserialize + + doc = {"doc" => -2147483648} + @b.serialize(doc) + assert_equal doc, @b.deserialize end def test_ordered_hash @@ -195,6 +207,21 @@ class BSONTest < Test::Unit::TestCase 0x00, 0x00, 0x00]) end + def test_overflow + doc = {"x" => 2**45} + assert_raise RangeError do + @b.serialize(doc) + end + + doc = {"x" => 2147483647} + assert_equal doc, @b.deserialize(@b.serialize(doc).to_a) + + doc["x"] = doc["x"] + 1 + assert_raise RangeError do + @b.serialize(doc) + end + end + def test_do_not_change_original_object val = OrderedHash.new val['not_id'] = 1