Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ internal static bool TryFind(ReadOnlySpan<char> pemData, out PemFields fields)
return false;
}

const int PostebStackBufferSize = 256;
Span<char> postebStackBuffer = stackalloc char[PostebStackBufferSize];
ReadOnlySpan<char> hyphen = PostEBPrefix.AsSpan(0, 1);
int areaOffset = 0;
int preebIndex;
while ((preebIndex = pemData.IndexOfByOffset(PreEBPrefix, areaOffset)) >= 0)
Expand Down Expand Up @@ -78,33 +77,77 @@ internal static bool TryFind(ReadOnlySpan<char> pemData, out PemFields fields)
}

int contentStartIndex = preebEndIndex + Ending.Length;
int postebLength = PostEBPrefix.Length + label.Length + Ending.Length;

Span<char> postebBuffer = postebLength > PostebStackBufferSize
? new char[postebLength]
: postebStackBuffer;
ReadOnlySpan<char> posteb = WritePostEB(label, postebBuffer);
int postebStartIndex = pemData.IndexOfByOffset(posteb, contentStartIndex);
// Find the next hyphen. If it's not the start of a PostEB, then it's not our
// PostEB, so we're not a PreEB, so resume searching there.
//
// There's no chance that the PreEB suffix is the start of a different PreEB,
// because IsValidLabel would have returned false if the label ended with whitespace,
// and it's a requirement that the PreEB prefix be preceded by whitespace (or be
// at index 0).
int nextHyphen = pemData.IndexOfByOffset(hyphen, contentStartIndex);

// No hyphen at all? No PostEB, so we're done.
if (nextHyphen < 0)
{
fields = default;
return false;
}

if (postebStartIndex < 0)
if (!pemData.Slice(nextHyphen).StartsWith(PostEBPrefix))
{
preebEndIndex = nextHyphen;
goto NextAfterLabel;
}

int postebStartIndex = nextHyphen;
int postebLabelStartIndex = postebStartIndex + PostEBPrefix.Length;
int postebEndIndex = pemData.IndexOfByOffset(Ending, postebLabelStartIndex);

if (postebEndIndex < 0)
{
fields = default;
return false;
}

ReadOnlySpan<char> postLabel = pemData[postebLabelStartIndex..postebEndIndex];

// If the PostEB label doesn't match, the document looks (at best), like
//
// -----BEGIN SOMELABEL-----
// base64
// -----END OTHERLABEL-----
//
// We know none of the purported base64 has any other hyphens, so no other PreEB
// could be there. Resume searching at postebEndIndex in case it says
// "-----END SOMELABEL\n-----BEGIN "
if (!postLabel.SequenceEqual(label))
{
preebEndIndex = postebEndIndex;
goto NextAfterLabel;
}

int postebLength = PostEBPrefix.Length + label.Length + Ending.Length;
int pemEndIndex = postebStartIndex + postebLength;

// The PostEB must either end at the end of the string, or
// have at least one white space character after it.
if (pemEndIndex < pemData.Length - 1 &&
!IsWhiteSpaceCharacter(pemData[pemEndIndex]))
{
// No matches can occur before the alleged post-EB suffix,
// so jump ahead.
preebEndIndex = postebEndIndex;
goto NextAfterLabel;
}

Range contentRange = contentStartIndex..postebStartIndex;

if (!TryCountBase64(pemData[contentRange], out int base64start, out int base64end, out int decodedSize))
{
// No matches can occur before the alleged post-EB suffix,
// so jump ahead.
preebEndIndex = postebEndIndex;
goto NextAfterLabel;
}

Expand All @@ -128,16 +171,6 @@ internal static bool TryFind(ReadOnlySpan<char> pemData, out PemFields fields)

fields = default;
return false;

static ReadOnlySpan<char> WritePostEB(ReadOnlySpan<char> label, Span<char> destination)
{
int size = PostEBPrefix.Length + label.Length + Ending.Length;
Debug.Assert(destination.Length >= size);
PostEBPrefix.AsSpan().CopyTo(destination);
label.CopyTo(destination.Slice(PostEBPrefix.Length));
Ending.AsSpan().CopyTo(destination.Slice(PostEBPrefix.Length + label.Length));
return destination.Slice(0, size);
}
}

private static int IndexOfByOffset(this ReadOnlySpan<char> str, ReadOnlySpan<char> value, int startPosition)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,69 @@ public void Find_Success_DecodeSize(string base64, int expectedSize)
Assert.Equal(base64, content[fields.Base64Data]);
}

[Fact]
public void Find_ManyBegins_OneEnd()
{
const int ContentLength = 4 * 1024 * 1024;
const string GoodPrefix = "-----BEGIN Y-----\n";
const string MinPayload = "base64AAAA==\n";

int div4 = 0;
int mod4 = 0;
int goodStart = 0;

// Build a string that looks like
// -----BEGIN X-----
// -----BEGIN X-----
// ...
// -----BEGIN X-----
// -----BEGIN Y-----
// [mod4 whitespace][content]
// -----END Y-----
StringBuilder builder = new StringBuilder(ContentLength);
const string Suffix = "-----END Y-----\n";
const string BadPrefix = "-----BEGIN X-----\n";

int tailLength = Suffix.Length + MinPayload.Length;
int reserved = GoodPrefix.Length + tailLength;
int badPrefixCount = (ContentLength - reserved) / BadPrefix.Length;

for (int i = 0; i < badPrefixCount; i++)
{
builder.Append(BadPrefix);
}

goodStart = builder.Length;
builder.Append(GoodPrefix);

int remain = builder.Capacity - builder.Length - tailLength;
div4 = Math.DivRem(remain, 4, out mod4);

for (int i = 0; i < mod4; i++)
{
builder.Append('\n');
}

for (int i = 0; i < div4; i++)
{
builder.Append("AAAA");
}

builder.Append(MinPayload);
builder.Append(Suffix);

string content = builder.ToString();

int expectedBase64Start = goodStart + GoodPrefix.Length + mod4;
int expectedBase64Len = 4 * div4 + MinPayload.Length - 1;

AssertPemFound(
content,
expectedLocation: goodStart..(ContentLength - 1),
expectedBase64: expectedBase64Start..(expectedBase64Start + expectedBase64Len),
expectedLabel: (goodStart + 11)..(goodStart + 12));
}

private PemFields AssertPemFound(
ReadOnlySpan<char> input,
Range expectedLocation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,7 @@ private static unsafe bool TryFindCore<TChar, T>(ReadOnlySpan<TChar> pemData, ou
return false;
}

const int PostebStackBufferSize = 256;
Span<TChar> postebStackBuffer = stackalloc TChar[PostebStackBufferSize];
ReadOnlySpan<TChar> hyphen = T.PostEBPrefix[0..1];
int areaOffset = 0;
int preebIndex;
while ((preebIndex = pemData.IndexOfByOffset(T.PreEBPrefix, areaOffset)) >= 0)
Expand Down Expand Up @@ -178,33 +177,77 @@ private static unsafe bool TryFindCore<TChar, T>(ReadOnlySpan<TChar> pemData, ou
}

int contentStartIndex = preebEndIndex + T.Ending.Length;
int postebLength = T.PostEBPrefix.Length + label.Length + T.Ending.Length;

Span<TChar> postebBuffer = postebLength > PostebStackBufferSize
? new TChar[postebLength]
: postebStackBuffer;
ReadOnlySpan<TChar> posteb = WritePostEB(label, postebBuffer);
int postebStartIndex = pemData.IndexOfByOffset(posteb, contentStartIndex);
// Find the next hyphen. If it's not the start of a PostEB, then it's not our
// PostEB, so we're not a PreEB, so resume searching there.
//
// There's no chance that the PreEB suffix is the start of a different PreEB,
// because IsValidLabel would have returned false if the label ended with whitespace,
// and it's a requirement that the PreEB prefix be preceded by whitespace (or be
// at index 0).
int nextHyphen = pemData.IndexOfByOffset(hyphen, contentStartIndex);

// No hyphen at all? No PostEB, so we're done.
if (nextHyphen < 0)
{
fields = default;
return false;
}

if (postebStartIndex < 0)
if (!pemData.Slice(nextHyphen).StartsWith(T.PostEBPrefix))
{
preebEndIndex = nextHyphen;
goto NextAfterLabel;
}

int postebStartIndex = nextHyphen;
int postebLabelStartIndex = postebStartIndex + T.PostEBPrefix.Length;
int postebEndIndex = pemData.IndexOfByOffset(T.Ending, postebLabelStartIndex);

if (postebEndIndex < 0)
{
fields = default;
return false;
}

ReadOnlySpan<TChar> postLabel = pemData[postebLabelStartIndex..postebEndIndex];

// If the PostEB label doesn't match, the document looks (at best), like
//
// -----BEGIN SOMELABEL-----
// base64
// -----END OTHERLABEL-----
//
// We know none of the purported base64 has any other hyphens, so no other PreEB
// could be there. Resume searching at postebEndIndex in case it says
// "-----END SOMELABEL\n-----BEGIN "
if (!postLabel.SequenceEqual(label))
{
preebEndIndex = postebEndIndex;
goto NextAfterLabel;
}

int postebLength = T.PostEBPrefix.Length + label.Length + T.Ending.Length;
int pemEndIndex = postebStartIndex + postebLength;

// The PostEB must either end at the end of the string, or
// have at least one white space character after it.
if (pemEndIndex < pemData.Length - 1 &&
!IsWhiteSpaceCharacter(pemData[pemEndIndex], T.Whitespace))
{
// No matches can occur before the alleged post-EB suffix,
// so jump ahead.
preebEndIndex = postebEndIndex;
goto NextAfterLabel;
}

Range contentRange = contentStartIndex..postebStartIndex;

if (!TryCountBase64<TChar, T>(pemData[contentRange], out int base64start, out int base64end, out int decodedSize))
{
// No matches can occur before the alleged post-EB suffix,
// so jump ahead.
preebEndIndex = postebEndIndex;
goto NextAfterLabel;
}

Expand All @@ -228,16 +271,6 @@ private static unsafe bool TryFindCore<TChar, T>(ReadOnlySpan<TChar> pemData, ou

fields = default;
return false;

static ReadOnlySpan<TChar> WritePostEB(ReadOnlySpan<TChar> label, Span<TChar> destination)
{
int size = T.PostEBPrefix.Length + label.Length + T.Ending.Length;
Debug.Assert(destination.Length >= size);
T.PostEBPrefix.CopyTo(destination);
label.CopyTo(destination.Slice(T.PostEBPrefix.Length));
T.Ending.CopyTo(destination.Slice(T.PostEBPrefix.Length + label.Length));
return destination.Slice(0, size);
}
}

private static int IndexOfByOffset<TChar>(this ReadOnlySpan<TChar> str, ReadOnlySpan<TChar> value, int startPosition)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,74 @@ public void Find_Success_DecodeSize(string base64, int expectedSize)
AssertExtensions.SequenceEqual(Create(base64), content[fields.Base64Data]);
}

[Fact]
public void Find_ManyBegins_OneEnd()
{
const int ContentLength = 4 * 1024 * 1024;
const string GoodPrefix = "-----BEGIN Y-----\n";
const string MinPayload = "base64AAAA==\n";

int div4 = 0;
int mod4 = 0;
int goodStart = 0;

// Build a string that looks like
// -----BEGIN X-----
// -----BEGIN X-----
// ...
// -----BEGIN X-----
// -----BEGIN Y-----
// [mod4 whitespace][content]
// -----END Y-----
string content = string.Create(
ContentLength,
0,
(span, _) =>
{
string suffix = "-----END Y-----\n";
ReadOnlySpan<char> badPrefix = "-----BEGIN X-----\n";
ReadOnlySpan<char> goodPrefix = GoodPrefix;
ReadOnlySpan<char> minPayload = MinPayload;

int tailLength = suffix.Length + minPayload.Length;
int reserved = goodPrefix.Length + tailLength;
int badPrefixCount = (span.Length - reserved) / badPrefix.Length;

for (int i = 0; i < badPrefixCount; i++)
{
badPrefix.CopyTo(span);
span = span.Slice(badPrefix.Length);
}

goodStart = ContentLength - span.Length;
goodPrefix.CopyTo(span);
span = span.Slice(goodPrefix.Length);

int remain = span.Length - tailLength;
(div4, mod4) = int.DivRem(remain, 4);

span.Slice(0, mod4).Fill('\n');
span = span.Slice(mod4);

span.Slice(0, 4 * div4).Fill('A');
span = span.Slice(4 * div4);

minPayload.CopyTo(span);
span = span.Slice(minPayload.Length);

suffix.CopyTo(span);
});

int expectedBase64Start = goodStart + GoodPrefix.Length + mod4;
int expectedBase64Len = 4 * div4 + MinPayload.Length - 1;

AssertPemFound(
content,
expectedLocation: goodStart .. (ContentLength - 1),
expectedBase64: expectedBase64Start .. (expectedBase64Start + expectedBase64Len),
expectedLabel: (goodStart + 11) .. (goodStart + 12));
}

private PemFields AssertPemFound(
string input,
Range expectedLocation,
Expand Down
Loading