H/QPackEncoder overloads accepting Encoding (#24448)

This commit is contained in:
Miha Zupan 2020-07-30 22:11:58 +02:00 committed by GitHub
parent b9e663bffb
commit e3501ddd4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 186 additions and 73 deletions

View File

@ -4,6 +4,7 @@
#nullable enable
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace System.Net.Http.HPack
{
@ -96,7 +97,7 @@ namespace System.Net.Http.HPack
if (IntegerEncoder.Encode(index, 4, destination, out int indexLength))
{
Debug.Assert(indexLength >= 1);
if (EncodeStringLiteral(value, destination.Slice(indexLength), out int nameLength))
if (EncodeStringLiteral(value, valueEncoding: null, destination.Slice(indexLength), out int nameLength))
{
bytesWritten = indexLength + nameLength;
return true;
@ -128,7 +129,7 @@ namespace System.Net.Http.HPack
if (IntegerEncoder.Encode(index, 4, destination, out int indexLength))
{
Debug.Assert(indexLength >= 1);
if (EncodeStringLiteral(value, destination.Slice(indexLength), out int nameLength))
if (EncodeStringLiteral(value, valueEncoding: null, destination.Slice(indexLength), out int nameLength))
{
bytesWritten = indexLength + nameLength;
return true;
@ -160,7 +161,7 @@ namespace System.Net.Http.HPack
if (IntegerEncoder.Encode(index, 6, destination, out int indexLength))
{
Debug.Assert(indexLength >= 1);
if (EncodeStringLiteral(value, destination.Slice(indexLength), out int nameLength))
if (EncodeStringLiteral(value, valueEncoding: null, destination.Slice(indexLength), out int nameLength))
{
bytesWritten = indexLength + nameLength;
return true;
@ -276,7 +277,7 @@ namespace System.Net.Http.HPack
{
destination[0] = mask;
if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength) &&
EncodeStringLiteral(value, destination.Slice(1 + nameLength), out int valueLength))
EncodeStringLiteral(value, valueEncoding: null, destination.Slice(1 + nameLength), out int valueLength))
{
bytesWritten = 1 + nameLength + valueLength;
return true;
@ -289,6 +290,11 @@ namespace System.Net.Http.HPack
/// <summary>Encodes a "Literal Header Field without Indexing - New Name".</summary>
public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, ReadOnlySpan<string> values, string separator, Span<byte> destination, out int bytesWritten)
{
return EncodeLiteralHeaderFieldWithoutIndexingNewName(name, values, separator, valueEncoding: null, destination, out bytesWritten);
}
public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, ReadOnlySpan<string> values, string separator, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten)
{
// From https://tools.ietf.org/html/rfc7541#section-6.2.2
// ------------------------------------------------------
@ -309,7 +315,7 @@ namespace System.Net.Http.HPack
{
destination[0] = 0;
if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength) &&
EncodeStringLiterals(values, separator, destination.Slice(1 + nameLength), out int valueLength))
EncodeStringLiterals(values, separator, valueEncoding, destination.Slice(1 + nameLength), out int valueLength))
{
bytesWritten = 1 + nameLength + valueLength;
return true;
@ -395,27 +401,20 @@ namespace System.Net.Http.HPack
return false;
}
private static bool EncodeStringLiteralValue(string value, Span<byte> destination, out int bytesWritten)
private static void EncodeValueStringPart(string value, Span<byte> destination)
{
if (value.Length <= destination.Length)
{
for (int i = 0; i < value.Length; i++)
{
char c = value[i];
if ((c & 0xFF80) != 0)
{
throw new HttpRequestException(SR.net_http_request_invalid_char_encoding);
}
Debug.Assert(destination.Length >= value.Length);
destination[i] = (byte)c;
for (int i = 0; i < value.Length; i++)
{
char c = value[i];
if ((c & 0xFF80) != 0)
{
throw new HttpRequestException(SR.net_http_request_invalid_char_encoding);
}
bytesWritten = value.Length;
return true;
destination[i] = (byte)c;
}
bytesWritten = 0;
return false;
}
public static bool EncodeStringLiteral(ReadOnlySpan<byte> value, Span<byte> destination, out int bytesWritten)
@ -453,6 +452,11 @@ namespace System.Net.Http.HPack
}
public static bool EncodeStringLiteral(string value, Span<byte> destination, out int bytesWritten)
{
return EncodeStringLiteral(value, valueEncoding: null, destination, out bytesWritten);
}
public static bool EncodeStringLiteral(string value, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten)
{
// From https://tools.ietf.org/html/rfc7541#section-5.2
// ------------------------------------------------------
@ -466,13 +470,28 @@ namespace System.Net.Http.HPack
if (destination.Length != 0)
{
destination[0] = 0; // TODO: Use Huffman encoding
if (IntegerEncoder.Encode(value.Length, 7, destination, out int integerLength))
int encodedStringLength = valueEncoding is null || ReferenceEquals(valueEncoding, Encoding.Latin1)
? value.Length
: valueEncoding.GetByteCount(value);
if (IntegerEncoder.Encode(encodedStringLength, 7, destination, out int integerLength))
{
Debug.Assert(integerLength >= 1);
if (EncodeStringLiteralValue(value, destination.Slice(integerLength), out int valueLength))
destination = destination.Slice(integerLength);
if (encodedStringLength <= destination.Length)
{
bytesWritten = integerLength + valueLength;
if (valueEncoding is null)
{
EncodeValueStringPart(value, destination);
}
else
{
int written = valueEncoding.GetBytes(value, destination);
Debug.Assert(written == encodedStringLength);
}
bytesWritten = integerLength + encodedStringLength;
return true;
}
}
@ -502,56 +521,87 @@ namespace System.Net.Http.HPack
}
public static bool EncodeStringLiterals(ReadOnlySpan<string> values, string? separator, Span<byte> destination, out int bytesWritten)
{
return EncodeStringLiterals(values, separator, valueEncoding: null, destination, out bytesWritten);
}
public static bool EncodeStringLiterals(ReadOnlySpan<string> values, string? separator, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten)
{
bytesWritten = 0;
if (values.Length == 0)
{
return EncodeStringLiteral("", destination, out bytesWritten);
return EncodeStringLiteral("", valueEncoding: null, destination, out bytesWritten);
}
else if (values.Length == 1)
{
return EncodeStringLiteral(values[0], destination, out bytesWritten);
return EncodeStringLiteral(values[0], valueEncoding, destination, out bytesWritten);
}
if (destination.Length != 0)
{
int valueLength = 0;
Debug.Assert(separator != null);
int valueLength;
// Calculate length of all parts and separators.
foreach (string part in values)
if (valueEncoding is null || ReferenceEquals(valueEncoding, Encoding.Latin1))
{
valueLength = checked((int)(valueLength + part.Length));
valueLength = checked((int)(values.Length - 1) * separator.Length);
foreach (string part in values)
{
valueLength = checked((int)(valueLength + part.Length));
}
}
else
{
valueLength = checked((int)(values.Length - 1) * valueEncoding.GetByteCount(separator));
foreach (string part in values)
{
valueLength = checked((int)(valueLength + valueEncoding.GetByteCount(part)));
}
}
Debug.Assert(separator != null);
valueLength = checked((int)(valueLength + (values.Length - 1) * separator.Length));
destination[0] = 0;
if (IntegerEncoder.Encode(valueLength, 7, destination, out int integerLength))
{
Debug.Assert(integerLength >= 1);
int encodedLength = 0;
for (int j = 0; j < values.Length; j++)
destination = destination.Slice(integerLength);
if (destination.Length >= valueLength)
{
if (j != 0 && !EncodeStringLiteralValue(separator, destination.Slice(integerLength), out encodedLength))
if (valueEncoding is null)
{
return false;
string value = values[0];
EncodeValueStringPart(value, destination);
destination = destination.Slice(value.Length);
for (int i = 1; i < values.Length; i++)
{
EncodeValueStringPart(separator, destination);
destination = destination.Slice(separator.Length);
value = values[i];
EncodeValueStringPart(value, destination);
destination = destination.Slice(value.Length);
}
}
else
{
int written = valueEncoding.GetBytes(values[0], destination);
destination = destination.Slice(written);
for (int i = 1; i < values.Length; i++)
{
written = valueEncoding.GetBytes(separator, destination);
destination = destination.Slice(written);
written = valueEncoding.GetBytes(values[i], destination);
destination = destination.Slice(written);
}
}
integerLength += encodedLength;
if (!EncodeStringLiteralValue(values[j], destination.Slice(integerLength), out encodedLength))
{
return false;
}
integerLength += encodedLength;
bytesWritten = integerLength + valueLength;
return true;
}
bytesWritten = integerLength;
return true;
}
}

View File

@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http.HPack;
using System.Text;
namespace System.Net.Http.QPack
{
@ -59,6 +60,11 @@ namespace System.Net.Http.QPack
// - T is constant 1 here, indicating a static table reference.
// - H is constant 0 here, as we do not yet perform Huffman coding.
public static bool EncodeLiteralHeaderFieldWithStaticNameReference(int index, string value, Span<byte> destination, out int bytesWritten)
{
return EncodeLiteralHeaderFieldWithStaticNameReference(index, value, valueEncoding: null, destination, out bytesWritten);
}
public static bool EncodeLiteralHeaderFieldWithStaticNameReference(int index, string value, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten)
{
// Requires at least two bytes (one for name reference header, one for value length)
if (destination.Length >= 2)
@ -68,7 +74,7 @@ namespace System.Net.Http.QPack
{
destination = destination.Slice(headerBytesWritten);
if (EncodeValueString(value, destination, out int valueBytesWritten))
if (EncodeValueString(value, valueEncoding, destination, out int valueBytesWritten))
{
bytesWritten = headerBytesWritten + valueBytesWritten;
return true;
@ -81,7 +87,7 @@ namespace System.Net.Http.QPack
}
/// <summary>
/// Encodes just the name part of a Literal Header Field With Static Name Reference. Must call <see cref="EncodeValueString(string, Span{byte}, out int)"/> after to encode the header's value.
/// Encodes just the name part of a Literal Header Field With Static Name Reference. Must call <see cref="EncodeValueString(string, Encoding?, Span{byte}, out int)"/> after to encode the header's value.
/// </summary>
public static byte[] EncodeLiteralHeaderFieldWithStaticNameReferenceToArray(int index)
{
@ -119,7 +125,12 @@ namespace System.Net.Http.QPack
// - H is constant 0 here, as we do not yet perform Huffman coding.
public static bool EncodeLiteralHeaderFieldWithoutNameReference(string name, string value, Span<byte> destination, out int bytesWritten)
{
if (EncodeNameString(name, destination, out int nameLength) && EncodeValueString(value, destination.Slice(nameLength), out int valueLength))
return EncodeLiteralHeaderFieldWithoutNameReference(name, value, valueEncoding: null, destination, out bytesWritten);
}
public static bool EncodeLiteralHeaderFieldWithoutNameReference(string name, string value, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten)
{
if (EncodeNameString(name, destination, out int nameLength) && EncodeValueString(value, valueEncoding, destination.Slice(nameLength), out int valueLength))
{
bytesWritten = nameLength + valueLength;
return true;
@ -136,7 +147,12 @@ namespace System.Net.Http.QPack
/// </summary>
public static bool EncodeLiteralHeaderFieldWithoutNameReference(string name, ReadOnlySpan<string> values, string valueSeparator, Span<byte> destination, out int bytesWritten)
{
if (EncodeNameString(name, destination, out int nameLength) && EncodeValueString(values, valueSeparator, destination.Slice(nameLength), out int valueLength))
return EncodeLiteralHeaderFieldWithoutNameReference(name, values, valueSeparator, valueEncoding: null, destination, out bytesWritten);
}
public static bool EncodeLiteralHeaderFieldWithoutNameReference(string name, ReadOnlySpan<string> values, string valueSeparator, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten)
{
if (EncodeNameString(name, destination, out int nameLength) && EncodeValueString(values, valueSeparator, valueEncoding, destination.Slice(nameLength), out int valueLength))
{
bytesWritten = nameLength + valueLength;
return true;
@ -147,7 +163,7 @@ namespace System.Net.Http.QPack
}
/// <summary>
/// Encodes just the value part of a Literawl Header Field Without Static Name Reference. Must call <see cref="EncodeValueString(string, Span{byte}, out int)"/> after to encode the header's value.
/// Encodes just the value part of a Literawl Header Field Without Static Name Reference. Must call <see cref="EncodeValueString(string, Encoding?, Span{byte}, out int)"/> after to encode the header's value.
/// </summary>
public static byte[] EncodeLiteralHeaderFieldWithoutNameReferenceToArray(string name)
{
@ -169,19 +185,32 @@ namespace System.Net.Http.QPack
return temp.Slice(0, bytesWritten).ToArray();
}
private static bool EncodeValueString(string s, Span<byte> buffer, out int length)
private static bool EncodeValueString(string s, Encoding? valueEncoding, Span<byte> buffer, out int length)
{
if (buffer.Length != 0)
{
buffer[0] = 0;
if (IntegerEncoder.Encode(s.Length, 7, buffer, out int nameLength))
int encodedStringLength = valueEncoding is null || ReferenceEquals(valueEncoding, Encoding.Latin1)
? s.Length
: valueEncoding.GetByteCount(s);
if (IntegerEncoder.Encode(encodedStringLength, 7, buffer, out int nameLength))
{
buffer = buffer.Slice(nameLength);
if (buffer.Length >= s.Length)
if (buffer.Length >= encodedStringLength)
{
EncodeValueStringPart(s, buffer);
if (valueEncoding is null)
{
EncodeValueStringPart(s, buffer);
}
else
{
int written = valueEncoding.GetBytes(s, buffer);
Debug.Assert(written == encodedStringLength);
}
length = nameLength + s.Length;
length = nameLength + encodedStringLength;
return true;
}
}
@ -195,25 +224,42 @@ namespace System.Net.Http.QPack
/// Encodes a value by concatenating a collection of strings, separated by a separator string.
/// </summary>
public static bool EncodeValueString(ReadOnlySpan<string> values, string? separator, Span<byte> buffer, out int length)
{
return EncodeValueString(values, separator, valueEncoding: null, buffer, out length);
}
public static bool EncodeValueString(ReadOnlySpan<string> values, string? separator, Encoding? valueEncoding, Span<byte> buffer, out int length)
{
if (values.Length == 1)
{
return EncodeValueString(values[0], buffer, out length);
return EncodeValueString(values[0], valueEncoding, buffer, out length);
}
if (values.Length == 0)
{
// TODO: this will be called with a string array from HttpHeaderCollection. Can we ever get a 0-length array from that? Assert if not.
return EncodeValueString(string.Empty, buffer, out length);
return EncodeValueString(string.Empty, valueEncoding: null, buffer, out length);
}
if (buffer.Length > 0)
{
Debug.Assert(separator != null);
int valueLength = separator.Length * (values.Length - 1);
for (int i = 0; i < values.Length; ++i)
int valueLength;
if (valueEncoding is null || ReferenceEquals(valueEncoding, Encoding.Latin1))
{
valueLength += values[i].Length;
valueLength = separator.Length * (values.Length - 1);
foreach (string part in values)
{
valueLength += part.Length;
}
}
else
{
valueLength = valueEncoding.GetByteCount(separator) * (values.Length - 1);
foreach (string part in values)
{
valueLength += valueEncoding.GetByteCount(part);
}
}
buffer[0] = 0;
@ -222,18 +268,35 @@ namespace System.Net.Http.QPack
buffer = buffer.Slice(nameLength);
if (buffer.Length >= valueLength)
{
string value = values[0];
EncodeValueStringPart(value, buffer);
buffer = buffer.Slice(value.Length);
for (int i = 1; i < values.Length; ++i)
if (valueEncoding is null)
{
EncodeValueStringPart(separator, buffer);
buffer = buffer.Slice(separator.Length);
value = values[i];
string value = values[0];
EncodeValueStringPart(value, buffer);
buffer = buffer.Slice(value.Length);
for (int i = 1; i < values.Length; i++)
{
EncodeValueStringPart(separator, buffer);
buffer = buffer.Slice(separator.Length);
value = values[i];
EncodeValueStringPart(value, buffer);
buffer = buffer.Slice(value.Length);
}
}
else
{
int written = valueEncoding.GetBytes(values[0], buffer);
buffer = buffer.Slice(written);
for (int i = 1; i < values.Length; i++)
{
written = valueEncoding.GetBytes(separator, buffer);
buffer = buffer.Slice(written);
written = valueEncoding.GetBytes(values[i], buffer);
buffer = buffer.Slice(written);
}
}
length = nameLength + valueLength;