Remove InplaceStringBuilder usages (#6163)
This commit is contained in:
parent
3477daf3c4
commit
0ae6cc8e88
|
|
@ -625,7 +625,7 @@ namespace Microsoft.Net.Http.Headers
|
||||||
{
|
{
|
||||||
input = RemoveQuotes(input);
|
input = RemoveQuotes(input);
|
||||||
|
|
||||||
// First pass to calculate the size of the InplaceStringBuilder
|
// First pass to calculate the size of the string
|
||||||
var backSlashCount = CountBackslashesForDecodingQuotedString(input);
|
var backSlashCount = CountBackslashesForDecodingQuotedString(input);
|
||||||
|
|
||||||
if (backSlashCount == 0)
|
if (backSlashCount == 0)
|
||||||
|
|
@ -633,26 +633,32 @@ namespace Microsoft.Net.Http.Headers
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
var stringBuilder = new InplaceStringBuilder(input.Length - backSlashCount);
|
return string.Create(input.Length - backSlashCount, input, (span, segment) =>
|
||||||
|
|
||||||
for (var i = 0; i < input.Length; i++)
|
|
||||||
{
|
{
|
||||||
if (i < input.Length - 1 && input[i] == '\\')
|
var spanIndex = 0;
|
||||||
|
var spanLength = span.Length;
|
||||||
|
for (var i = 0; i < segment.Length && (uint)spanIndex < (uint)spanLength; i++)
|
||||||
{
|
{
|
||||||
// If there is an backslash character as the last character in the string,
|
int nextIndex = i + 1;
|
||||||
// we will assume that it should be included literally in the unescaped string
|
if ((uint)nextIndex < (uint)segment.Length && segment[i] == '\\')
|
||||||
// Ex: "hello\\" => "hello\\"
|
{
|
||||||
// Also, if a sender adds a quoted pair like '\\''n',
|
// If there is an backslash character as the last character in the string,
|
||||||
// we will assume it is over escaping and just add a n to the string.
|
// we will assume that it should be included literally in the unescaped string
|
||||||
// Ex: "he\\llo" => "hello"
|
// Ex: "hello\\" => "hello\\"
|
||||||
stringBuilder.Append(input[i + 1]);
|
// Also, if a sender adds a quoted pair like '\\''n',
|
||||||
i++;
|
// we will assume it is over escaping and just add a n to the string.
|
||||||
continue;
|
// Ex: "he\\llo" => "hello"
|
||||||
}
|
span[spanIndex] = segment[nextIndex];
|
||||||
stringBuilder.Append(input[i]);
|
i++;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
span[spanIndex] = segment[i];
|
||||||
|
}
|
||||||
|
|
||||||
return stringBuilder.ToString();
|
spanIndex++;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int CountBackslashesForDecodingQuotedString(StringSegment input)
|
private static int CountBackslashesForDecodingQuotedString(StringSegment input)
|
||||||
|
|
@ -696,25 +702,27 @@ namespace Microsoft.Net.Http.Headers
|
||||||
// By calling this, we know that the string requires quotes around it to be a valid token.
|
// By calling this, we know that the string requires quotes around it to be a valid token.
|
||||||
var backSlashCount = CountAndCheckCharactersNeedingBackslashesWhenEncoding(input);
|
var backSlashCount = CountAndCheckCharactersNeedingBackslashesWhenEncoding(input);
|
||||||
|
|
||||||
var stringBuilder = new InplaceStringBuilder(input.Length + backSlashCount + 2); // 2 for quotes
|
// 2 for quotes
|
||||||
stringBuilder.Append('\"');
|
return string.Create(input.Length + backSlashCount + 2, input, (span, segment) => {
|
||||||
|
// Helps to elide the bounds check for span[0]
|
||||||
|
span[span.Length - 1] = span[0] = '\"';
|
||||||
|
|
||||||
for (var i = 0; i < input.Length; i++)
|
var spanIndex = 1;
|
||||||
{
|
for (var i = 0; i < segment.Length; i++)
|
||||||
if (input[i] == '\\' || input[i] == '\"')
|
|
||||||
{
|
{
|
||||||
stringBuilder.Append('\\');
|
if (segment[i] == '\\' || segment[i] == '\"')
|
||||||
|
{
|
||||||
|
span[spanIndex++] = '\\';
|
||||||
|
}
|
||||||
|
else if ((segment[i] <= 0x1F || segment[i] == 0x7F) && segment[i] != 0x09)
|
||||||
|
{
|
||||||
|
// Control characters are not allowed in a quoted-string, which include all characters
|
||||||
|
// below 0x1F (except for 0x09 (TAB)) and 0x7F.
|
||||||
|
throw new FormatException($"Invalid control character '{segment[i]}' in input.");
|
||||||
|
}
|
||||||
|
span[spanIndex++] = segment[i];
|
||||||
}
|
}
|
||||||
else if ((input[i] <= 0x1F || input[i] == 0x7F) && input[i] != 0x09)
|
});
|
||||||
{
|
|
||||||
// Control characters are not allowed in a quoted-string, which include all characters
|
|
||||||
// below 0x1F (except for 0x09 (TAB)) and 0x7F.
|
|
||||||
throw new FormatException($"Invalid control character '{input[i]}' in input.");
|
|
||||||
}
|
|
||||||
stringBuilder.Append(input[i]);
|
|
||||||
}
|
|
||||||
stringBuilder.Append('\"');
|
|
||||||
return stringBuilder.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int CountAndCheckCharactersNeedingBackslashesWhenEncoding(StringSegment input)
|
private static int CountAndCheckCharactersNeedingBackslashesWhenEncoding(StringSegment input)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.Contracts;
|
using System.Diagnostics.Contracts;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
@ -24,7 +25,8 @@ namespace Microsoft.Net.Http.Headers
|
||||||
private const string HttpOnlyToken = "httponly";
|
private const string HttpOnlyToken = "httponly";
|
||||||
private const string SeparatorToken = "; ";
|
private const string SeparatorToken = "; ";
|
||||||
private const string EqualsToken = "=";
|
private const string EqualsToken = "=";
|
||||||
private const string DefaultPath = "/"; // TODO: Used?
|
private const int ExpiresDateLength = 29;
|
||||||
|
private const string ExpiresDateFormat = "r";
|
||||||
|
|
||||||
private static readonly HttpHeaderParser<SetCookieHeaderValue> SingleValueParser
|
private static readonly HttpHeaderParser<SetCookieHeaderValue> SingleValueParser
|
||||||
= new GenericHeaderParser<SetCookieHeaderValue>(false, GetSetCookieLength);
|
= new GenericHeaderParser<SetCookieHeaderValue>(false, GetSetCookieLength);
|
||||||
|
|
@ -99,14 +101,11 @@ namespace Microsoft.Net.Http.Headers
|
||||||
{
|
{
|
||||||
var length = _name.Length + EqualsToken.Length + _value.Length;
|
var length = _name.Length + EqualsToken.Length + _value.Length;
|
||||||
|
|
||||||
string expires = null;
|
|
||||||
string maxAge = null;
|
string maxAge = null;
|
||||||
string sameSite = null;
|
|
||||||
|
|
||||||
if (Expires.HasValue)
|
if (Expires.HasValue)
|
||||||
{
|
{
|
||||||
expires = HeaderUtilities.FormatDate(Expires.GetValueOrDefault());
|
length += SeparatorToken.Length + ExpiresToken.Length + EqualsToken.Length + ExpiresDateLength;
|
||||||
length += SeparatorToken.Length + ExpiresToken.Length + EqualsToken.Length + expires.Length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MaxAge.HasValue)
|
if (MaxAge.HasValue)
|
||||||
|
|
@ -132,7 +131,7 @@ namespace Microsoft.Net.Http.Headers
|
||||||
|
|
||||||
if (SameSite != SameSiteMode.None)
|
if (SameSite != SameSiteMode.None)
|
||||||
{
|
{
|
||||||
sameSite = SameSite == SameSiteMode.Lax ? SameSiteLaxToken : SameSiteStrictToken;
|
var sameSite = SameSite == SameSiteMode.Lax ? SameSiteLaxToken : SameSiteStrictToken;
|
||||||
length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length;
|
length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,61 +140,75 @@ namespace Microsoft.Net.Http.Headers
|
||||||
length += SeparatorToken.Length + HttpOnlyToken.Length;
|
length += SeparatorToken.Length + HttpOnlyToken.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb = new InplaceStringBuilder(length);
|
return string.Create(length, (this, maxAge), (span, tuple) =>
|
||||||
|
|
||||||
sb.Append(_name);
|
|
||||||
sb.Append(EqualsToken);
|
|
||||||
sb.Append(_value);
|
|
||||||
|
|
||||||
if (expires != null)
|
|
||||||
{
|
{
|
||||||
AppendSegment(ref sb, ExpiresToken, expires);
|
var (headerValue, maxAgeValue) = tuple;
|
||||||
}
|
|
||||||
|
|
||||||
if (maxAge != null)
|
Append(ref span, headerValue._name);
|
||||||
{
|
Append(ref span, EqualsToken);
|
||||||
AppendSegment(ref sb, MaxAgeToken, maxAge);
|
Append(ref span, headerValue._value);
|
||||||
}
|
|
||||||
|
|
||||||
if (Domain != null)
|
if (headerValue.Expires is DateTimeOffset expiresValue)
|
||||||
{
|
{
|
||||||
AppendSegment(ref sb, DomainToken, Domain);
|
Append(ref span, SeparatorToken);
|
||||||
}
|
Append(ref span, ExpiresToken);
|
||||||
|
Append(ref span, EqualsToken);
|
||||||
|
|
||||||
if (Path != null)
|
var formatted = expiresValue.TryFormat(span, out var charsWritten, ExpiresDateFormat);
|
||||||
{
|
span = span.Slice(charsWritten);
|
||||||
AppendSegment(ref sb, PathToken, Path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Secure)
|
Debug.Assert(formatted);
|
||||||
{
|
}
|
||||||
AppendSegment(ref sb, SecureToken, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SameSite != SameSiteMode.None)
|
if (maxAgeValue != null)
|
||||||
{
|
{
|
||||||
AppendSegment(ref sb, SameSiteToken, sameSite);
|
AppendSegment(ref span, MaxAgeToken, maxAgeValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HttpOnly)
|
if (headerValue.Domain != null)
|
||||||
{
|
{
|
||||||
AppendSegment(ref sb, HttpOnlyToken, null);
|
AppendSegment(ref span, DomainToken, headerValue.Domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.ToString();
|
if (headerValue.Path != null)
|
||||||
|
{
|
||||||
|
AppendSegment(ref span, PathToken, headerValue.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerValue.Secure)
|
||||||
|
{
|
||||||
|
AppendSegment(ref span, SecureToken, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerValue.SameSite != SameSiteMode.None)
|
||||||
|
{
|
||||||
|
AppendSegment(ref span, SameSiteToken, headerValue.SameSite == SameSiteMode.Lax ? SameSiteLaxToken : SameSiteStrictToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerValue.HttpOnly)
|
||||||
|
{
|
||||||
|
AppendSegment(ref span, HttpOnlyToken, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AppendSegment(ref InplaceStringBuilder builder, StringSegment name, StringSegment value)
|
private static void AppendSegment(ref Span<char> span, StringSegment name, StringSegment value)
|
||||||
{
|
{
|
||||||
builder.Append(SeparatorToken);
|
Append(ref span, SeparatorToken);
|
||||||
builder.Append(name);
|
Append(ref span, name.AsSpan());
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
builder.Append(EqualsToken);
|
Append(ref span, EqualsToken);
|
||||||
builder.Append(value);
|
Append(ref span, value.AsSpan());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void Append(ref Span<char> span, ReadOnlySpan<char> other)
|
||||||
|
{
|
||||||
|
other.CopyTo(span);
|
||||||
|
span = span.Slice(other.Length);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Append string representation of this <see cref="SetCookieHeaderValue"/> to given
|
/// Append string representation of this <see cref="SetCookieHeaderValue"/> to given
|
||||||
/// <paramref name="builder"/>.
|
/// <paramref name="builder"/>.
|
||||||
|
|
@ -452,14 +465,14 @@ namespace Microsoft.Net.Http.Headers
|
||||||
result.HttpOnly = true;
|
result.HttpOnly = true;
|
||||||
}
|
}
|
||||||
// extension-av = <any CHAR except CTLs or ";">
|
// extension-av = <any CHAR except CTLs or ";">
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO: skiping it for now to avoid parsing failure? Store it in a list?
|
// TODO: skiping it for now to avoid parsing failure? Store it in a list?
|
||||||
// = (no spaces)
|
// = (no spaces)
|
||||||
if (!ReadEqualsSign(input, ref offset))
|
if (!ReadEqualsSign(input, ref offset))
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
ReadToSemicolonOrEnd(input, ref offset);
|
ReadToSemicolonOrEnd(input, ref offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
namespace Microsoft.Net.Http.Headers
|
||||||
|
{
|
||||||
|
public class HeaderUtilitiesBenchmark
|
||||||
|
{
|
||||||
|
[Benchmark]
|
||||||
|
public StringSegment UnescapeAsQuotedString()
|
||||||
|
{
|
||||||
|
return HeaderUtilities.UnescapeAsQuotedString("\"hello\\\"foo\\\\bar\\\\baz\\\\\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public StringSegment EscapeAsQuotedString()
|
||||||
|
{
|
||||||
|
return HeaderUtilities.EscapeAsQuotedString("\"hello\\\"foo\\\\bar\\\\baz\\\\\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
{
|
{
|
||||||
internal static class ViewPath
|
internal static class ViewPath
|
||||||
|
|
@ -23,23 +21,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
length++;
|
length++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder = new InplaceStringBuilder(length);
|
return string.Create(length, (path, addLeadingSlash), (span, tuple) =>
|
||||||
if (addLeadingSlash)
|
|
||||||
{
|
{
|
||||||
builder.Append('/');
|
var (pathValue, addLeadingSlashValue) = tuple;
|
||||||
}
|
var spanIndex = 0;
|
||||||
|
|
||||||
for (var i = 0; i < path.Length; i++)
|
if (addLeadingSlashValue)
|
||||||
{
|
|
||||||
var ch = path[i];
|
|
||||||
if (ch == '\\')
|
|
||||||
{
|
{
|
||||||
ch = '/';
|
span[spanIndex++] = '/';
|
||||||
}
|
}
|
||||||
builder.Append(ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.ToString();
|
foreach (var ch in pathValue)
|
||||||
|
{
|
||||||
|
span[spanIndex++] = ch == '\\' ? '/' : ch;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,12 +144,18 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
Debug.Assert(!string.IsNullOrEmpty(viewEnginePath));
|
Debug.Assert(!string.IsNullOrEmpty(viewEnginePath));
|
||||||
Debug.Assert(viewEnginePath.StartsWith("/", StringComparison.Ordinal));
|
Debug.Assert(viewEnginePath.StartsWith("/", StringComparison.Ordinal));
|
||||||
|
|
||||||
var builder = new InplaceStringBuilder(1 + areaName.Length + viewEnginePath.Length);
|
return string.Create(1 + areaName.Length + viewEnginePath.Length, (areaName, viewEnginePath), (span, tuple) =>
|
||||||
builder.Append('/');
|
{
|
||||||
builder.Append(areaName);
|
var (areaNameValue, viewEnginePathValue) = tuple;
|
||||||
builder.Append(viewEnginePath);
|
|
||||||
|
|
||||||
return builder.ToString();
|
span[0] = '/';
|
||||||
|
span = span.Slice(1);
|
||||||
|
|
||||||
|
areaNameValue.AsSpan().CopyTo(span);
|
||||||
|
span = span.Slice(areaNameValue.Length);
|
||||||
|
|
||||||
|
viewEnginePathValue.AsSpan().CopyTo(span);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SelectorModel CreateSelectorModel(string prefix, string routeTemplate)
|
private static SelectorModel CreateSelectorModel(string prefix, string routeTemplate)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue