/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Shared/runtime/Http2/Hpack/HPackEncoder.cs
643 строки
27 KB
Tratcher
Sync shared code from runtime
09 май 2026, 11:34
09 май 2026, 11:34
34d4442
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable enable using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Text; namespace System.Net.Http.HPack { internal static partial class HPackEncoder { // Things we should add: // * Huffman encoding // // Things we should consider adding: // * Dynamic table encoding: // This would make the encoder stateful, which complicates things significantly. // Additionally, it's not clear exactly what strings we would add to the dynamic table // without some additional guidance from the user about this. // So for now, don't do dynamic encoding. /// <summary>Encodes an "Indexed Header Field".</summary> public static bool EncodeIndexedHeaderField(int index, Span<byte> destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.1 // ---------------------------------------------------- // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | 1 | Index (7+) | // +---+---------------------------+ if (destination.Length != 0) { destination[0] = 0x80; return IntegerEncoder.Encode(index, 7, destination, out bytesWritten); } bytesWritten = 0; return false; } /// <summary>Encodes the status code of a response to the :status field.</summary> public static bool EncodeStatusHeader(int statusCode, Span<byte> destination, out int bytesWritten) { // Bytes written depend on whether the status code value maps directly to an index if (H2StaticTable.TryGetStatusIndex(statusCode, out var index)) { // Status codes which exist in the HTTP/2 StaticTable. return EncodeIndexedHeaderField(index, destination, out bytesWritten); } else { // If the status code doesn't have a static index then we need to include the full value. // Write a status index and then the number bytes as a string literal. if (!EncodeLiteralHeaderFieldWithoutIndexing(H2StaticTable.Status200, destination, out var nameLength)) { bytesWritten = 0; return false; } var statusBytes = StatusCodes.ToStatusBytes(statusCode); if (!EncodeStringLiteral(statusBytes, destination.Slice(nameLength), out var valueLength)) { bytesWritten = 0; return false; } bytesWritten = nameLength + valueLength; return true; } } /// <summary>Encodes a "Literal Header Field without Indexing".</summary> public static bool EncodeLiteralHeaderFieldWithoutIndexing(int index, string value, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.2 // ------------------------------------------------------ // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | 0 | 0 | 0 | 0 | Index (4+) | // +---+---+-----------------------+ // | H | Value Length (7+) | // +---+---------------------------+ // | Value String (Length octets) | // +-------------------------------+ if ((uint)destination.Length >= 2) { destination[0] = 0; if (IntegerEncoder.Encode(index, 4, destination, out int indexLength)) { Debug.Assert(indexLength >= 1); if (EncodeStringLiteral(value, valueEncoding, destination.Slice(indexLength), out int nameLength)) { bytesWritten = indexLength + nameLength; return true; } } } bytesWritten = 0; return false; } /// <summary>Encodes a "Literal Header Field never Indexing".</summary> public static bool EncodeLiteralHeaderFieldNeverIndexing(int index, string value, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.3 // ------------------------------------------------------ // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | 0 | 0 | 0 | 1 | Index (4+) | // +---+---+-----------------------+ // | H | Value Length (7+) | // +---+---------------------------+ // | Value String (Length octets) | // +-------------------------------+ if ((uint)destination.Length >= 2) { destination[0] = 0x10; if (IntegerEncoder.Encode(index, 4, destination, out int indexLength)) { Debug.Assert(indexLength >= 1); if (EncodeStringLiteral(value, valueEncoding, destination.Slice(indexLength), out int nameLength)) { bytesWritten = indexLength + nameLength; return true; } } } bytesWritten = 0; return false; } /// <summary>Encodes a "Literal Header Field with Indexing".</summary> public static bool EncodeLiteralHeaderFieldIndexing(int index, string value, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.2 // ------------------------------------------------------ // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | 0 | 1 | Index (6+) | // +---+---+-----------------------+ // | H | Value Length (7+) | // +---+---------------------------+ // | Value String (Length octets) | // +-------------------------------+ if ((uint)destination.Length >= 2) { destination[0] = 0x40; if (IntegerEncoder.Encode(index, 6, destination, out int indexLength)) { Debug.Assert(indexLength >= 1); if (EncodeStringLiteral(value, valueEncoding, destination.Slice(indexLength), out int nameLength)) { bytesWritten = indexLength + nameLength; return true; } } } bytesWritten = 0; return false; } /// <summary> /// Encodes a "Literal Header Field without Indexing", but only the index portion; /// a subsequent call to <c>EncodeStringLiteral</c> must be used to encode the associated value. /// </summary> public static bool EncodeLiteralHeaderFieldWithoutIndexing(int index, Span<byte> destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.2 // ------------------------------------------------------ // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | 0 | 0 | 0 | 0 | Index (4+) | // +---+---+-----------------------+ // // ... expected after this: // // | H | Value Length (7+) | // +---+---------------------------+ // | Value String (Length octets) | // +-------------------------------+ if ((uint)destination.Length != 0) { destination[0] = 0; if (IntegerEncoder.Encode(index, 4, destination, out int indexLength)) { Debug.Assert(indexLength >= 1); bytesWritten = indexLength; return true; } } bytesWritten = 0; return false; } /// <summary>Encodes a "Literal Header Field with Indexing - New Name".</summary> public static bool EncodeLiteralHeaderFieldIndexingNewName(string name, string value, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.2 // ------------------------------------------------------ // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | 0 | 1 | 0 | // +---+---+-----------------------+ // | H | Name Length (7+) | // +---+---------------------------+ // | Name String (Length octets) | // +---+---------------------------+ // | H | Value Length (7+) | // +---+---------------------------+ // | Value String (Length octets) | // +-------------------------------+ return EncodeLiteralHeaderNewNameCore(0x40, name, value, valueEncoding, destination, out bytesWritten); } /// <summary>Encodes a "Literal Header Field without Indexing - New Name".</summary> public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, string value, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.2 // ------------------------------------------------------ // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | 0 | 0 | 0 | 0 | 0 | // +---+---+-----------------------+ // | H | Name Length (7+) | // +---+---------------------------+ // | Name String (Length octets) | // +---+---------------------------+ // | H | Value Length (7+) | // +---+---------------------------+ // | Value String (Length octets) | // +-------------------------------+ return EncodeLiteralHeaderNewNameCore(0, name, value, valueEncoding, destination, out bytesWritten); } /// <summary>Encodes a "Literal Header Field never Indexing - New Name".</summary> public static bool EncodeLiteralHeaderFieldNeverIndexingNewName(string name, string value, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.3 // ------------------------------------------------------ // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | 0 | 0 | 0 | 1 | 0 | // +---+---+-----------------------+ // | H | Name Length (7+) | // +---+---------------------------+ // | Name String (Length octets) | // +---+---------------------------+ // | H | Value Length (7+) | // +---+---------------------------+ // | Value String (Length octets) | // +-------------------------------+ return EncodeLiteralHeaderNewNameCore(0x10, name, value, valueEncoding, destination, out bytesWritten); } private static bool EncodeLiteralHeaderNewNameCore(byte mask, string name, string value, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten) { if ((uint)destination.Length >= 3) { destination[0] = mask; if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength) && EncodeStringLiteral(value, valueEncoding, destination.Slice(1 + nameLength), out int valueLength)) { bytesWritten = 1 + nameLength + valueLength; return true; } } bytesWritten = 0; return false; } /// <summary>Encodes a "Literal Header Field without Indexing - New Name".</summary> public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, ReadOnlySpan<string> values, byte[] separator, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.2 // ------------------------------------------------------ // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | 0 | 0 | 0 | 0 | 0 | // +---+---+-----------------------+ // | H | Name Length (7+) | // +---+---------------------------+ // | Name String (Length octets) | // +---+---------------------------+ // | H | Value Length (7+) | // +---+---------------------------+ // | Value String (Length octets) | // +-------------------------------+ if ((uint)destination.Length >= 3) { destination[0] = 0; if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength) && EncodeStringLiterals(values, separator, valueEncoding, destination.Slice(1 + nameLength), out int valueLength)) { bytesWritten = 1 + nameLength + valueLength; return true; } } bytesWritten = 0; return false; } /// <summary> /// Encodes a "Literal Header Field without Indexing - New Name", but only the name portion; /// a subsequent call to <c>EncodeStringLiteral</c> must be used to encode the associated value. /// </summary> public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, Span<byte> destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.2 // ------------------------------------------------------ // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | 0 | 0 | 0 | 0 | 0 | // +---+---+-----------------------+ // | H | Name Length (7+) | // +---+---------------------------+ // | Name String (Length octets) | // +---+---------------------------+ // // ... expected after this: // // | H | Value Length (7+) | // +---+---------------------------+ // | Value String (Length octets) | // +-------------------------------+ if ((uint)destination.Length >= 2) { destination[0] = 0; if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength)) { bytesWritten = 1 + nameLength; return true; } } bytesWritten = 0; return false; } private static bool EncodeLiteralHeaderName(string value, Span<byte> destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-5.2 // ------------------------------------------------------ // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | H | String Length (7+) | // +---+---------------------------+ // | String Data (Length octets) | // +-------------------------------+ Debug.Assert(Ascii.IsValid(value)); if (destination.Length != 0) { destination[0] = 0; // TODO: Use Huffman encoding if (IntegerEncoder.Encode(value.Length, 7, destination, out int integerLength)) { Debug.Assert(integerLength >= 1); destination = destination.Slice(integerLength); if (value.Length <= destination.Length) { OperationStatus status = Ascii.ToLower(value, destination, out int valueBytesWritten); Debug.Assert(status == OperationStatus.Done); Debug.Assert(valueBytesWritten == value.Length); bytesWritten = integerLength + value.Length; return true; } } } bytesWritten = 0; return false; } private static void EncodeValueStringPart(string value, Span<byte> destination) { Debug.Assert(destination.Length >= value.Length); OperationStatus status = Ascii.FromUtf16(value, destination, out int bytesWritten); if (status == OperationStatus.InvalidData) { throw new HttpRequestException(SR.net_http_request_invalid_char_encoding); } Debug.Assert(status == OperationStatus.Done); Debug.Assert(bytesWritten == value.Length); } public static bool EncodeStringLiteral(ReadOnlySpan<byte> value, Span<byte> destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-5.2 // ------------------------------------------------------ // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | H | String Length (7+) | // +---+---------------------------+ // | String Data (Length octets) | // +-------------------------------+ if (destination.Length != 0) { destination[0] = 0; // TODO: Use Huffman encoding if (IntegerEncoder.Encode(value.Length, 7, destination, out int integerLength)) { Debug.Assert(integerLength >= 1); destination = destination.Slice(integerLength); if (value.Length <= destination.Length) { // Note: No validation. Bytes should have already been validated. value.CopyTo(destination); bytesWritten = integerLength + value.Length; return true; } } } bytesWritten = 0; return false; } 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 // ------------------------------------------------------ // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | H | String Length (7+) | // +---+---------------------------+ // | String Data (Length octets) | // +-------------------------------+ if (destination.Length != 0) { destination[0] = 0; // TODO: Use Huffman encoding 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); destination = destination.Slice(integerLength); if (encodedStringLength <= destination.Length) { if (valueEncoding is null) { EncodeValueStringPart(value, destination); } else { int written = valueEncoding.GetBytes(value, destination); Debug.Assert(written == encodedStringLength); } bytesWritten = integerLength + encodedStringLength; return true; } } } bytesWritten = 0; return false; } public static bool EncodeDynamicTableSizeUpdate(int value, Span<byte> destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.3 // ---------------------------------------------------- // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | 0 | 0 | 1 | Max size (5+) | // +---+---------------------------+ if (destination.Length != 0) { destination[0] = 0x20; return IntegerEncoder.Encode(value, 5, destination, out bytesWritten); } bytesWritten = 0; return false; } public static bool EncodeStringLiterals(ReadOnlySpan<string> values, byte[]? separator, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten) { bytesWritten = 0; if (values.Length == 0) { return EncodeStringLiteral("", valueEncoding: null, destination, out bytesWritten); } else if (values.Length == 1) { return EncodeStringLiteral(values[0], valueEncoding, destination, out bytesWritten); } if (destination.Length != 0) { Debug.Assert(separator != null); Debug.Assert(Ascii.IsValid(separator)); int valueLength = checked((values.Length - 1) * separator.Length); // Calculate length of all values. if (valueEncoding is null || ReferenceEquals(valueEncoding, Encoding.Latin1)) { foreach (string part in values) { valueLength = checked(valueLength + part.Length); } } else { foreach (string part in values) { valueLength = checked(valueLength + valueEncoding.GetByteCount(part)); } } destination[0] = 0; if (IntegerEncoder.Encode(valueLength, 7, destination, out int integerLength)) { Debug.Assert(integerLength >= 1); destination = destination.Slice(integerLength); if (destination.Length >= valueLength) { if (valueEncoding is null) { string value = values[0]; EncodeValueStringPart(value, destination); destination = destination.Slice(value.Length); for (int i = 1; i < values.Length; i++) { separator.CopyTo(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++) { separator.CopyTo(destination); destination = destination.Slice(separator.Length); written = valueEncoding.GetBytes(values[i], destination); destination = destination.Slice(written); } } bytesWritten = integerLength + valueLength; return true; } } } return false; } /// <summary> /// Encodes a "Literal Header Field without Indexing" to a new array, but only the index portion; /// a subsequent call to <c>EncodeStringLiteral</c> must be used to encode the associated value. /// </summary> public static unsafe byte[] EncodeLiteralHeaderFieldWithoutIndexingToAllocatedArray(int index) { Span<byte> span = stackalloc byte[256]; bool success = EncodeLiteralHeaderFieldWithoutIndexing(index, span, out int length); Debug.Assert(success, $"Stack-allocated space was too small for index '{index}'."); return span.Slice(0, length).ToArray(); } /// <summary> /// Encodes a "Literal Header Field without Indexing - New Name" to a new array, but only the name portion; /// a subsequent call to <c>EncodeStringLiteral</c> must be used to encode the associated value. /// </summary> public static unsafe byte[] EncodeLiteralHeaderFieldWithoutIndexingNewNameToAllocatedArray(string name) { Span<byte> span = stackalloc byte[256]; bool success = EncodeLiteralHeaderFieldWithoutIndexingNewName(name, span, out int length); Debug.Assert(success, $"Stack-allocated space was too small for \"{name}\"."); return span.Slice(0, length).ToArray(); } /// <summary>Encodes a "Literal Header Field without Indexing" to a new array.</summary> public static unsafe byte[] EncodeLiteralHeaderFieldWithoutIndexingToAllocatedArray(int index, string value) { Span<byte> span = #if DEBUG stackalloc byte[4]; // to validate growth algorithm #else stackalloc byte[512]; #endif while (true) { if (EncodeLiteralHeaderFieldWithoutIndexing(index, value, valueEncoding: null, span, out int length)) { return span.Slice(0, length).ToArray(); } // This is a rare path, only used once per HTTP/2 connection and only // for very long host names. Just allocate rather than complicate // the code with ArrayPool usage. In practice we should never hit this, // as hostnames should be <= 255 characters. span = new byte[span.Length * 2]; } } } }