Skip to content

Commit 105a823

Browse files
committed
Throw an exception when there are multiple roots instead of misparsing
Ref #134
1 parent 39e237e commit 105a823

8 files changed

Lines changed: 129 additions & 1 deletion

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System.Text;
2+
3+
namespace ValveKeyValue.Test
4+
{
5+
class MultipleRootObjectsTestCase
6+
{
7+
const string Kv3Header = "<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->\n";
8+
9+
[Test]
10+
public void SecondRootObjectInTextThrows()
11+
{
12+
using var stream = TestDataHelper.OpenResource("Text.multiple_root_objects.vdf");
13+
var ex = Assert.Throws<KeyValueException>(
14+
() => KVSerializer.Create(KVSerializationFormat.KeyValues1Text).Deserialize(stream));
15+
Assert.That(ex.Message, Does.Contain("line 6, column 1"));
16+
}
17+
18+
[Test]
19+
public void SecondTopLevelPairInTextThrows()
20+
{
21+
const string text = "\"a\"\t\"1\"\n\"b\"\t\"2\"\n";
22+
23+
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(text));
24+
Assert.Throws<KeyValueException>(
25+
() => KVSerializer.Create(KVSerializationFormat.KeyValues1Text).Deserialize(stream));
26+
}
27+
28+
[TestCase("\"x\" = \"y\"", TestName = "Kv3TrailingKeyValuePairThrows")]
29+
[TestCase("\"x\"", TestName = "Kv3TrailingStringThrows")]
30+
[TestCase("{ b = 2 }", TestName = "Kv3SecondRootObjectThrows")]
31+
[TestCase("[1, 2]", TestName = "Kv3SecondRootArrayThrows")]
32+
[TestCase("#[00112233]", TestName = "Kv3TrailingBinaryBlobThrows")]
33+
[TestCase("resource:\"path/to/file.vmdl\"", TestName = "Kv3TrailingFlaggedValueThrows")]
34+
public void DataAfterRootValueInKV3Throws(string trailer)
35+
{
36+
var text = Kv3Header + "{\n\ta = 1\n}\n" + trailer + "\n";
37+
38+
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(text));
39+
Assert.Throws<KeyValueException>(
40+
() => KVSerializer.Create(KVSerializationFormat.KeyValues3Text).Deserialize(stream));
41+
}
42+
43+
[Test]
44+
public void SecondRootObjectInBinaryThrows()
45+
{
46+
var data = new byte[]
47+
{
48+
0x00, // object: first
49+
0x66, 0x69, 0x72, 0x73, 0x74, 0x00,
50+
0x01, // string: a = 1
51+
0x61, 0x00,
52+
0x31, 0x00,
53+
0x08, // end of first
54+
0x00, // object: second
55+
0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x00,
56+
0x01, // string: c = 3
57+
0x63, 0x00,
58+
0x33, 0x00,
59+
0x08, // end of second
60+
0x08, // end of document
61+
};
62+
63+
using var stream = new MemoryStream(data);
64+
Assert.Throws<KeyValueException>(
65+
() => KVSerializer.Create(KVSerializationFormat.KeyValues1Binary).Deserialize(stream));
66+
}
67+
}
68+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"first"
2+
{
3+
"a" "1"
4+
"b" "2"
5+
}
6+
"second"
7+
{
8+
"c" "3"
9+
}
10+
"third"
11+
{
12+
"d" "4"
13+
}

ValveKeyValue/ValveKeyValue.Test/Text/InvalidSyntaxTestCase.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class InvalidSyntaxTestCase
1414
[TestCase("partial_partialvalue")]
1515
[TestCase("partial_noclose")]
1616
[TestCase("invalid_zerobracerepeated")]
17+
[TestCase("multiple_root_objects")]
1718
public void InvalidTextSyntaxThrowsKeyValueException(string resourceName)
1819
{
1920
using var stream = TestDataHelper.OpenResource("Text." + resourceName + ".vdf");

ValveKeyValue/ValveKeyValue/Deserialization/KeyValues1/KV1BinaryReader.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public KVHeader ReadHeader()
4242

4343
try
4444
{
45-
ReadObjectCore();
45+
ReadRootObject();
4646
}
4747
catch (IOException ex)
4848
{
@@ -69,6 +69,23 @@ public void Dispose()
6969
}
7070
}
7171

72+
void ReadRootObject()
73+
{
74+
var type = ReadNextNodeType();
75+
76+
if (type == endMarker)
77+
{
78+
return;
79+
}
80+
81+
ReadValue(type);
82+
83+
if (ReadNextNodeType() != endMarker)
84+
{
85+
throw new KeyValueException("Found data after the root object, documents with multiple root objects are not supported.");
86+
}
87+
}
88+
7289
void ReadObjectCore()
7390
{
7491
KV1BinaryNodeType type = ReadNextNodeType();

ValveKeyValue/ValveKeyValue/Deserialization/KeyValues1/KV1TextReader.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ void ReadText(string text)
140140
{
141141
// If we're after a value when we find more text, then we must be starting a new key/value pair.
142142
case KV1TextReaderState.InObjectAfterValue:
143+
if (stateMachine.IsAtDocumentLevel)
144+
{
145+
throw new KeyValueException($"Found data after the root object at {tokenReader.PreviousTokenPosition}, documents with multiple root objects are not supported.");
146+
}
147+
143148
FinalizeCurrentObject(@explicit: false);
144149
stateMachine.PushObject();
145150
SetObjectKey(text);

ValveKeyValue/ValveKeyValue/Deserialization/KeyValues1/KV1TextReaderStateMachine.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public KV1TextReaderStateMachine()
1616

1717
public bool IsInObject => states.Count > 0;
1818

19+
public bool IsAtDocumentLevel => states.Count == 1;
20+
1921
public bool IsAtStart => states.Count == 1 && CurrentObject.States.Count == 1 && Current == KV1TextReaderState.InObjectBeforeKey;
2022

2123
public void PushObject() => states.Push(new KVPartialState<KV1TextReaderState>());

ValveKeyValue/ValveKeyValue/Deserialization/KeyValues3/KV3TextReader.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ void ReadComma()
156156

157157
void ReadFlag(string text)
158158
{
159+
ThrowIfAfterRootValue();
160+
159161
if (stateMachine.Current != KV3TextReaderState.InArray && stateMachine.Current != KV3TextReaderState.InObjectAfterKey)
160162
{
161163
throw new InvalidOperationException($"Attempted to read flag while in state {stateMachine.Current}.");
@@ -166,8 +168,20 @@ void ReadFlag(string text)
166168
stateMachine.SetFlag(flag);
167169
}
168170

171+
// The document pseudo-object is only ever in InObjectBeforeKey once the root value
172+
// has been fully read, so any further data-bearing token at that point is trailing garbage.
173+
void ThrowIfAfterRootValue()
174+
{
175+
if (stateMachine.IsAtDocumentLevel && stateMachine.Current == KV3TextReaderState.InObjectBeforeKey)
176+
{
177+
throw new KeyValueException($"Found data after the root value at {tokenReader.PreviousTokenPosition}, documents with multiple root values are not supported.");
178+
}
179+
}
180+
169181
void ReadText(string text)
170182
{
183+
ThrowIfAfterRootValue();
184+
171185
switch (stateMachine.Current)
172186
{
173187
case KV3TextReaderState.InArray:
@@ -200,6 +214,8 @@ void ReadText(string text)
200214

201215
void ReadBinaryBlob(string text)
202216
{
217+
ThrowIfAfterRootValue();
218+
203219
var bytes = HexStringHelper.ParseHexStringAsByteArray(text);
204220
var value = KVObject.Blob(bytes);
205221
value.Flag = stateMachine.GetAndResetFlag();
@@ -228,6 +244,8 @@ void ReadBinaryBlob(string text)
228244

229245
void BeginNewArray()
230246
{
247+
ThrowIfAfterRootValue();
248+
231249
if (stateMachine.Current != KV3TextReaderState.InArray && stateMachine.Current != KV3TextReaderState.InObjectAfterKey)
232250
{
233251
throw new InvalidOperationException($"Attempted to begin new array while in state {stateMachine.Current}.");
@@ -266,6 +284,8 @@ void SetObjectKey(string name)
266284

267285
void BeginNewObject()
268286
{
287+
ThrowIfAfterRootValue();
288+
269289
if (stateMachine.Current != KV3TextReaderState.InArray && stateMachine.Current != KV3TextReaderState.InObjectAfterKey)
270290
{
271291
throw new InvalidOperationException($"Attempted to begin new object while in state {stateMachine.Current}.");

ValveKeyValue/ValveKeyValue/Deserialization/KeyValues3/KV3TextReaderStateMachine.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public KV3TextReaderStateMachine()
1616

1717
public bool IsInObject => states.Count > 0;
1818

19+
public bool IsAtDocumentLevel => states.Count == 1;
20+
1921
public bool IsInArray => states.Count > 0 && CurrentObject.IsArray;
2022

2123
public void PushObject() => states.Push(new KVPartialState<KV3TextReaderState>());

0 commit comments

Comments
 (0)