Skip to content

Commit 5ec4bba

Browse files
committed
CSHARP-2303: When reading an extended JSON BsonTimestamp be lenient about element order.
1 parent 4892dcc commit 5ec4bba

File tree

2 files changed

+92
-27
lines changed

2 files changed

+92
-27
lines changed

src/MongoDB.Bson/IO/JsonReader.cs

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,36 +1717,71 @@ private BsonValue ParseTimestampExtendedJson()
17171717

17181718
private BsonValue ParseTimestampExtendedJsonNewRepresentation()
17191719
{
1720-
VerifyString("t");
1721-
VerifyToken(":");
1722-
var secondsSinceEpochToken = PopToken();
1723-
int secondsSinceEpoch;
1724-
if (secondsSinceEpochToken.IsNumber)
1725-
{
1726-
secondsSinceEpoch = secondsSinceEpochToken.Int32Value;
1727-
}
1728-
else
1720+
int? timestamp = null;
1721+
int? increment = null;
1722+
1723+
while (true)
17291724
{
1730-
var message = string.Format("JSON reader expected an integer but found '{0}'.", secondsSinceEpochToken.Lexeme);
1731-
throw new FormatException(message);
1725+
var token = PopToken();
1726+
if (token.Type != JsonTokenType.String && token.Type != JsonTokenType.UnquotedString)
1727+
{
1728+
throw new FormatException($"JSON reader expected an element name but found '{token.Lexeme}'.");
1729+
}
1730+
var name = token.StringValue;
1731+
1732+
token = PopToken();
1733+
if (token.Type != JsonTokenType.Colon)
1734+
{
1735+
throw new FormatException($"JSON reader expected ':' but found '{name}'.");
1736+
}
1737+
1738+
token = PopToken();
1739+
if (token.Type != JsonTokenType.Int32)
1740+
{
1741+
throw new FormatException($"JSON reader expected an integer but found '{token.Lexeme}'.");
1742+
}
1743+
var value = token.Int32Value;
1744+
1745+
switch (name)
1746+
{
1747+
case "t":
1748+
timestamp = value;
1749+
break;
1750+
1751+
case "i":
1752+
increment = value;
1753+
break;
1754+
1755+
default:
1756+
throw new FormatException($"JSON reader expected 't' or 'i' element names but found '{name}'.");
1757+
}
1758+
1759+
token = PopToken();
1760+
if (token.Type == JsonTokenType.Comma)
1761+
{
1762+
continue;
1763+
}
1764+
else if (token.Type == JsonTokenType.EndObject)
1765+
{
1766+
break;
1767+
}
1768+
else
1769+
{
1770+
throw new FormatException($"JSON reader expected ',' or '}}' but found '{token.Lexeme}'.");
1771+
}
17321772
}
1733-
VerifyToken(",");
1734-
VerifyString("i");
1735-
VerifyToken(":");
1736-
var incrementToken = PopToken();
1737-
int increment;
1738-
if (incrementToken.IsNumber)
1773+
VerifyToken("}");
1774+
1775+
if (!timestamp.HasValue)
17391776
{
1740-
increment = incrementToken.Int32Value;
1777+
throw new FormatException("JSON reader did not find the required \"t\" element.");
17411778
}
1742-
else
1779+
if (!increment.HasValue)
17431780
{
1744-
var message = string.Format("JSON reader expected an integer but found '{0}'.", incrementToken.Lexeme);
1745-
throw new FormatException(message);
1781+
throw new FormatException("JSON reader did not find the required \"i\" element.");
17461782
}
1747-
VerifyToken("}");
1748-
VerifyToken("}");
1749-
return new BsonTimestamp(secondsSinceEpoch, increment);
1783+
1784+
return new BsonTimestamp(timestamp.Value, increment.Value);
17501785
}
17511786

17521787
private BsonValue ParseTimestampExtendedJsonOldRepresentation(JsonToken valueToken)

tests/MongoDB.Bson.Tests/IO/JsonReaderTests.cs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -996,10 +996,11 @@ public void TestTimestampConstructor()
996996
Assert.Equal(json, BsonSerializer.Deserialize<BsonTimestamp>(new StringReader(json)).ToJson());
997997
}
998998

999-
[Fact]
1000-
public void TestTimestampExtendedJsonNewRepresentation()
999+
[Theory]
1000+
[InlineData("{ \"$timestamp\" : { \"t\" : 1, \"i\" : 2 } }")]
1001+
[InlineData("{ \"$timestamp\" : { \"i\" : 2, \"t\" : 1 } }")]
1002+
public void TestTimestampExtendedJsonNewRepresentation(string json)
10011003
{
1002-
var json = "{ \"$timestamp\" : { \"t\" : 1, \"i\" : 2 } }";
10031004
using (_bsonReader = new JsonReader(json))
10041005
{
10051006
Assert.Equal(BsonType.Timestamp, _bsonReader.ReadBsonType());
@@ -1010,6 +1011,35 @@ public void TestTimestampExtendedJsonNewRepresentation()
10101011
Assert.Equal(canonicalJson, BsonSerializer.Deserialize<BsonTimestamp>(new StringReader(json)).ToJson());
10111012
}
10121013

1014+
[Theory]
1015+
// truncated input
1016+
[InlineData("{ \"$timestamp\" : {")]
1017+
[InlineData("{ \"$timestamp\" : { \"t\"")]
1018+
[InlineData("{ \"$timestamp\" : { \"t\" :")]
1019+
[InlineData("{ \"$timestamp\" : { \"t\" : 1")]
1020+
[InlineData("{ \"$timestamp\" : { \"t\" : 1,")]
1021+
[InlineData("{ \"$timestamp\" : { \"t\" : 1, \"i\"")]
1022+
[InlineData("{ \"$timestamp\" : { \"t\" : 1, \"i\" :")]
1023+
[InlineData("{ \"$timestamp\" : { \"t\" : 1, \"i\" : 2")]
1024+
[InlineData("{ \"$timestamp\" : { \"t\" : 1, \"i\" : 2 }")]
1025+
// valid JSON but not a valid extended JSON BsonTimestamp
1026+
[InlineData("{ \"$timestamp\" : { }")]
1027+
[InlineData("{ \"$timestamp\" : { \"t\" : 1 } }")]
1028+
[InlineData("{ \"$timestamp\" : { \"i\" : 2 } }")]
1029+
[InlineData("{ \"$timestamp\" : { \"t\" : 1, \"i\" : 2.0 } }")]
1030+
[InlineData("{ \"$timestamp\" : { \"t\" : 1.0, \"x\" : 2 } }")]
1031+
[InlineData("{ \"$timestamp\" : { \"t\" : 1, \"x\" : 2 } }")]
1032+
[InlineData("{ \"$timestamp\" : { \"i\" : 2, \"x\" : 1 } }")]
1033+
public void TestTimestampExtendedJsonNewRepresentationWhenInvalid(string json)
1034+
{
1035+
using (_bsonReader = new JsonReader(json))
1036+
{
1037+
var exception = Record.Exception(() => _bsonReader.ReadBsonType());
1038+
1039+
exception.Should().BeOfType<FormatException>();
1040+
}
1041+
}
1042+
10131043
[Fact]
10141044
public void TestTimestampExtendedJsonOldRepresentation()
10151045
{

0 commit comments

Comments
 (0)