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);
|
||||
|
||||
// First pass to calculate the size of the InplaceStringBuilder
|
||||
// First pass to calculate the size of the string
|
||||
var backSlashCount = CountBackslashesForDecodingQuotedString(input);
|
||||
|
||||
if (backSlashCount == 0)
|
||||
|
|
@ -633,26 +633,32 @@ namespace Microsoft.Net.Http.Headers
|
|||
return input;
|
||||
}
|
||||
|
||||
var stringBuilder = new InplaceStringBuilder(input.Length - backSlashCount);
|
||||
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
return string.Create(input.Length - backSlashCount, input, (span, segment) =>
|
||||
{
|
||||
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,
|
||||
// we will assume that it should be included literally in the unescaped string
|
||||
// Ex: "hello\\" => "hello\\"
|
||||
// Also, if a sender adds a quoted pair like '\\''n',
|
||||
// we will assume it is over escaping and just add a n to the string.
|
||||
// Ex: "he\\llo" => "hello"
|
||||
stringBuilder.Append(input[i + 1]);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
stringBuilder.Append(input[i]);
|
||||
}
|
||||
int nextIndex = i + 1;
|
||||
if ((uint)nextIndex < (uint)segment.Length && segment[i] == '\\')
|
||||
{
|
||||
// If there is an backslash character as the last character in the string,
|
||||
// we will assume that it should be included literally in the unescaped string
|
||||
// Ex: "hello\\" => "hello\\"
|
||||
// Also, if a sender adds a quoted pair like '\\''n',
|
||||
// we will assume it is over escaping and just add a n to the string.
|
||||
// Ex: "he\\llo" => "hello"
|
||||
span[spanIndex] = segment[nextIndex];
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
span[spanIndex] = segment[i];
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
spanIndex++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.
|
||||
var backSlashCount = CountAndCheckCharactersNeedingBackslashesWhenEncoding(input);
|
||||
|
||||
var stringBuilder = new InplaceStringBuilder(input.Length + backSlashCount + 2); // 2 for quotes
|
||||
stringBuilder.Append('\"');
|
||||
// 2 for quotes
|
||||
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++)
|
||||
{
|
||||
if (input[i] == '\\' || input[i] == '\"')
|
||||
var spanIndex = 1;
|
||||
for (var i = 0; i < segment.Length; 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)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
|
@ -24,7 +25,8 @@ namespace Microsoft.Net.Http.Headers
|
|||
private const string HttpOnlyToken = "httponly";
|
||||
private const string SeparatorToken = "; ";
|
||||
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
|
||||
= new GenericHeaderParser<SetCookieHeaderValue>(false, GetSetCookieLength);
|
||||
|
|
@ -99,14 +101,11 @@ namespace Microsoft.Net.Http.Headers
|
|||
{
|
||||
var length = _name.Length + EqualsToken.Length + _value.Length;
|
||||
|
||||
string expires = null;
|
||||
string maxAge = null;
|
||||
string sameSite = null;
|
||||
|
||||
if (Expires.HasValue)
|
||||
{
|
||||
expires = HeaderUtilities.FormatDate(Expires.GetValueOrDefault());
|
||||
length += SeparatorToken.Length + ExpiresToken.Length + EqualsToken.Length + expires.Length;
|
||||
length += SeparatorToken.Length + ExpiresToken.Length + EqualsToken.Length + ExpiresDateLength;
|
||||
}
|
||||
|
||||
if (MaxAge.HasValue)
|
||||
|
|
@ -132,7 +131,7 @@ namespace Microsoft.Net.Http.Headers
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -141,61 +140,75 @@ namespace Microsoft.Net.Http.Headers
|
|||
length += SeparatorToken.Length + HttpOnlyToken.Length;
|
||||
}
|
||||
|
||||
var sb = new InplaceStringBuilder(length);
|
||||
|
||||
sb.Append(_name);
|
||||
sb.Append(EqualsToken);
|
||||
sb.Append(_value);
|
||||
|
||||
if (expires != null)
|
||||
return string.Create(length, (this, maxAge), (span, tuple) =>
|
||||
{
|
||||
AppendSegment(ref sb, ExpiresToken, expires);
|
||||
}
|
||||
var (headerValue, maxAgeValue) = tuple;
|
||||
|
||||
if (maxAge != null)
|
||||
{
|
||||
AppendSegment(ref sb, MaxAgeToken, maxAge);
|
||||
}
|
||||
Append(ref span, headerValue._name);
|
||||
Append(ref span, EqualsToken);
|
||||
Append(ref span, headerValue._value);
|
||||
|
||||
if (Domain != null)
|
||||
{
|
||||
AppendSegment(ref sb, DomainToken, Domain);
|
||||
}
|
||||
if (headerValue.Expires is DateTimeOffset expiresValue)
|
||||
{
|
||||
Append(ref span, SeparatorToken);
|
||||
Append(ref span, ExpiresToken);
|
||||
Append(ref span, EqualsToken);
|
||||
|
||||
if (Path != null)
|
||||
{
|
||||
AppendSegment(ref sb, PathToken, Path);
|
||||
}
|
||||
var formatted = expiresValue.TryFormat(span, out var charsWritten, ExpiresDateFormat);
|
||||
span = span.Slice(charsWritten);
|
||||
|
||||
if (Secure)
|
||||
{
|
||||
AppendSegment(ref sb, SecureToken, null);
|
||||
}
|
||||
Debug.Assert(formatted);
|
||||
}
|
||||
|
||||
if (SameSite != SameSiteMode.None)
|
||||
{
|
||||
AppendSegment(ref sb, SameSiteToken, sameSite);
|
||||
}
|
||||
if (maxAgeValue != null)
|
||||
{
|
||||
AppendSegment(ref span, MaxAgeToken, maxAgeValue);
|
||||
}
|
||||
|
||||
if (HttpOnly)
|
||||
{
|
||||
AppendSegment(ref sb, HttpOnlyToken, null);
|
||||
}
|
||||
if (headerValue.Domain != 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);
|
||||
builder.Append(name);
|
||||
Append(ref span, SeparatorToken);
|
||||
Append(ref span, name.AsSpan());
|
||||
if (value != null)
|
||||
{
|
||||
builder.Append(EqualsToken);
|
||||
builder.Append(value);
|
||||
Append(ref span, EqualsToken);
|
||||
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>
|
||||
/// Append string representation of this <see cref="SetCookieHeaderValue"/> to given
|
||||
/// <paramref name="builder"/>.
|
||||
|
|
@ -452,14 +465,14 @@ namespace Microsoft.Net.Http.Headers
|
|||
result.HttpOnly = true;
|
||||
}
|
||||
// extension-av = <any CHAR except CTLs or ";">
|
||||
else
|
||||
{
|
||||
else
|
||||
{
|
||||
// TODO: skiping it for now to avoid parsing failure? Store it in a list?
|
||||
// = (no spaces)
|
||||
if (!ReadEqualsSign(input, ref offset))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
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.
|
||||
// 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
|
||||
{
|
||||
internal static class ViewPath
|
||||
|
|
@ -23,23 +21,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
length++;
|
||||
}
|
||||
|
||||
var builder = new InplaceStringBuilder(length);
|
||||
if (addLeadingSlash)
|
||||
return string.Create(length, (path, addLeadingSlash), (span, tuple) =>
|
||||
{
|
||||
builder.Append('/');
|
||||
}
|
||||
var (pathValue, addLeadingSlashValue) = tuple;
|
||||
var spanIndex = 0;
|
||||
|
||||
for (var i = 0; i < path.Length; i++)
|
||||
{
|
||||
var ch = path[i];
|
||||
if (ch == '\\')
|
||||
if (addLeadingSlashValue)
|
||||
{
|
||||
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(viewEnginePath.StartsWith("/", StringComparison.Ordinal));
|
||||
|
||||
var builder = new InplaceStringBuilder(1 + areaName.Length + viewEnginePath.Length);
|
||||
builder.Append('/');
|
||||
builder.Append(areaName);
|
||||
builder.Append(viewEnginePath);
|
||||
return string.Create(1 + areaName.Length + viewEnginePath.Length, (areaName, viewEnginePath), (span, tuple) =>
|
||||
{
|
||||
var (areaNameValue, viewEnginePathValue) = tuple;
|
||||
|
||||
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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue