Skip to content

Commit d489aa3

Browse files
glen-84michaelstaib
authored andcommitted
Updated node ID serializers to ensure correct padding while parsing (#8123)
1 parent b07cb02 commit d489aa3

File tree

4 files changed

+120
-4
lines changed

4 files changed

+120
-4
lines changed

src/HotChocolate/Core/src/Types/Types/Relay/Serialization/DefaultNodeIdSerializer.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,25 @@ public NodeId Parse(string formattedId, INodeIdRuntimeTypeLookup runtimeTypeLook
204204
}
205205
}
206206

207+
// Ensure correct padding.
208+
var firstPaddingIndex = span.IndexOf((byte)'=');
209+
var nonPaddedLength = firstPaddingIndex == -1 ? span.Length : firstPaddingIndex;
210+
var actualPadding = firstPaddingIndex == -1 ? 0 : span.Length - firstPaddingIndex;
211+
var expectedPadding = (4 - nonPaddedLength % 4) % 4;
212+
213+
if (actualPadding != expectedPadding)
214+
{
215+
Span<byte> correctedSpan = stackalloc byte[nonPaddedLength + expectedPadding];
216+
span[..nonPaddedLength].CopyTo(correctedSpan);
217+
218+
for (var i = nonPaddedLength; i < correctedSpan.Length; i++)
219+
{
220+
correctedSpan[i] = (byte)'=';
221+
}
222+
223+
span = correctedSpan;
224+
}
225+
207226
var operationStatus = Base64.DecodeFromUtf8InPlace(span, out var written);
208227
if (operationStatus != OperationStatus.Done)
209228
{
@@ -280,6 +299,25 @@ public NodeId Parse(string formattedId, Type runtimeType)
280299
}
281300
}
282301

302+
// Ensure correct padding.
303+
var firstPaddingIndex = span.IndexOf((byte)'=');
304+
var nonPaddedLength = firstPaddingIndex == -1 ? span.Length : firstPaddingIndex;
305+
var actualPadding = firstPaddingIndex == -1 ? 0 : span.Length - firstPaddingIndex;
306+
var expectedPadding = (4 - nonPaddedLength % 4) % 4;
307+
308+
if (actualPadding != expectedPadding)
309+
{
310+
Span<byte> correctedSpan = stackalloc byte[nonPaddedLength + expectedPadding];
311+
span[..nonPaddedLength].CopyTo(correctedSpan);
312+
313+
for (var i = nonPaddedLength; i < correctedSpan.Length; i++)
314+
{
315+
correctedSpan[i] = (byte)'=';
316+
}
317+
318+
span = correctedSpan;
319+
}
320+
283321
var operationStatus = Base64.DecodeFromUtf8InPlace(span, out var written);
284322
if (operationStatus != OperationStatus.Done)
285323
{

src/HotChocolate/Core/src/Types/Types/Relay/Serialization/OptimizedNodeIdSerializer.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,25 @@ public unsafe NodeId Parse(string formattedId, INodeIdRuntimeTypeLookup runtimeT
106106
}
107107
}
108108

109+
// Ensure correct padding.
110+
var firstPaddingIndex = span.IndexOf((byte)'=');
111+
var nonPaddedLength = firstPaddingIndex == -1 ? span.Length : firstPaddingIndex;
112+
var actualPadding = firstPaddingIndex == -1 ? 0 : span.Length - firstPaddingIndex;
113+
var expectedPadding = (4 - nonPaddedLength % 4) % 4;
114+
115+
if (actualPadding != expectedPadding)
116+
{
117+
Span<byte> correctedSpan = stackalloc byte[nonPaddedLength + expectedPadding];
118+
span[..nonPaddedLength].CopyTo(correctedSpan);
119+
120+
for (var i = nonPaddedLength; i < correctedSpan.Length; i++)
121+
{
122+
correctedSpan[i] = (byte)'=';
123+
}
124+
125+
span = correctedSpan;
126+
}
127+
109128
var operationStatus = Base64.DecodeFromUtf8InPlace(span, out var written);
110129
if (operationStatus != OperationStatus.Done)
111130
{
@@ -181,6 +200,25 @@ public unsafe NodeId Parse(string formattedId, Type runtimeType)
181200
}
182201
}
183202

203+
// Ensure correct padding.
204+
var firstPaddingIndex = span.IndexOf((byte)'=');
205+
var nonPaddedLength = firstPaddingIndex == -1 ? span.Length : firstPaddingIndex;
206+
var actualPadding = firstPaddingIndex == -1 ? 0 : span.Length - firstPaddingIndex;
207+
var expectedPadding = (4 - nonPaddedLength % 4) % 4;
208+
209+
if (actualPadding != expectedPadding)
210+
{
211+
Span<byte> correctedSpan = stackalloc byte[nonPaddedLength + expectedPadding];
212+
span[..nonPaddedLength].CopyTo(correctedSpan);
213+
214+
for (var i = nonPaddedLength; i < correctedSpan.Length; i++)
215+
{
216+
correctedSpan[i] = (byte)'=';
217+
}
218+
219+
span = correctedSpan;
220+
}
221+
184222
var operationStatus = Base64.DecodeFromUtf8InPlace(span, out var written);
185223
if (operationStatus != OperationStatus.Done)
186224
{

src/HotChocolate/Core/test/Types.Tests/Types/Relay/DefaultNodeIdSerializerTests.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ public void Parse_Throws_NodeIdInvalidFormatException_On_InvalidBase64Input()
524524
var serializer = CreateSerializer(new StringNodeIdValueSerializer());
525525

526526
Assert.Throws<NodeIdInvalidFormatException>(
527-
() => serializer.Parse("Rm9vOkJhcg", typeof(string)));
527+
() => serializer.Parse("!", typeof(string)));
528528
}
529529

530530
[Fact]
@@ -535,7 +535,27 @@ public void ParseOnRuntimeLookup_Throws_NodeIdInvalidFormatException_On_InvalidB
535535
var serializer = CreateSerializer(new StringNodeIdValueSerializer());
536536

537537
Assert.Throws<NodeIdInvalidFormatException>(
538-
() => serializer.Parse("Rm9vOkJhcg", lookup.Object));
538+
() => serializer.Parse("!", lookup.Object));
539+
}
540+
541+
[Theory]
542+
[InlineData("RW50aXR5OjE")] // No padding (length: 11).
543+
[InlineData("RW50aXR5OjE=")] // Correct padding (length: 12).
544+
[InlineData("RW50aXR5OjE==")] // Excess padding (length: 13).
545+
[InlineData("RW50aXR5OjE===")] // Excess padding (length: 14).
546+
[InlineData("RW50aXR5OjE====")] // Excess padding (length: 15).
547+
[InlineData("RW50aXR5OjE=====")] // Excess padding (length: 16).
548+
public void Parse_Ensures_Correct_Padding(string id)
549+
{
550+
var lookup = new Mock<INodeIdRuntimeTypeLookup>();
551+
lookup.Setup(t => t.GetNodeIdRuntimeType("Entity")).Returns(typeof(int));
552+
var serializer = CreateSerializer(new Int32NodeIdValueSerializer());
553+
554+
void Act1() => serializer.Parse(id, typeof(int));
555+
void Act2() => serializer.Parse(id, lookup.Object);
556+
557+
Assert.Null(Record.Exception(Act1));
558+
Assert.Null(Record.Exception(Act2));
539559
}
540560

541561
[Fact]

src/HotChocolate/Core/test/Types.Tests/Types/Relay/OptimizedNodeIdSerializerTests.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ public void Parse_Throws_NodeIdInvalidFormatException_On_InvalidBase64Input()
446446
var serializer = CreateSerializer("Foo", new StringNodeIdValueSerializer());
447447

448448
Assert.Throws<NodeIdInvalidFormatException>(
449-
() => serializer.Parse("Rm9vOkJhcg", typeof(string)));
449+
() => serializer.Parse("!", typeof(string)));
450450
}
451451

452452
[Fact]
@@ -458,7 +458,27 @@ public void ParseOnRuntimeLookup_Throws_NodeIdInvalidFormatException_On_InvalidB
458458
var serializer = CreateSerializer("Foo", new StringNodeIdValueSerializer());
459459

460460
Assert.Throws<NodeIdInvalidFormatException>(
461-
() => serializer.Parse("Rm9vOkJhcg", lookup.Object));
461+
() => serializer.Parse("!", lookup.Object));
462+
}
463+
464+
[Theory]
465+
[InlineData("RW50aXR5OjE")] // No padding (length: 11).
466+
[InlineData("RW50aXR5OjE=")] // Correct padding (length: 12).
467+
[InlineData("RW50aXR5OjE==")] // Excess padding (length: 13).
468+
[InlineData("RW50aXR5OjE===")] // Excess padding (length: 14).
469+
[InlineData("RW50aXR5OjE====")] // Excess padding (length: 15).
470+
[InlineData("RW50aXR5OjE=====")] // Excess padding (length: 16).
471+
public void Parse_Ensures_Correct_Padding(string id)
472+
{
473+
var lookup = new Mock<INodeIdRuntimeTypeLookup>();
474+
lookup.Setup(t => t.GetNodeIdRuntimeType(default)).Returns(default(Type));
475+
var serializer = CreateSerializer("Entity", new Int32NodeIdValueSerializer());
476+
477+
void Act1() => serializer.Parse(id, typeof(int));
478+
void Act2() => serializer.Parse(id, lookup.Object);
479+
480+
Assert.Null(Record.Exception(Act1));
481+
Assert.Null(Record.Exception(Act2));
462482
}
463483

464484
[Fact]

0 commit comments

Comments
 (0)