/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs
248 строк
11 KB
Korolev Dmitry
fix http/2 header payload padding parsing (#65729)
13 мар 2026, 00:52
Не верифицирован
13 мар 2026, 00:52
8842e08
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.Diagnostics; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; /// <summary> /// Deserializes a binary buffer into an <see cref="Http2Frame"/>. /// </summary> /// <remarks> /// Populates an existing <see cref="Http2Frame"/> instance, rather than creating a new one. /// </remarks> internal static class Http2FrameReader { /* https://tools.ietf.org/html/rfc7540#section-4.1 +-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Frame Payload (0...) ... +---------------------------------------------------------------+ */ public const int HeaderLength = 9; private const int TypeOffset = 3; private const int FlagsOffset = 4; private const int StreamIdOffset = 5; public const int SettingSize = 6; // 2 bytes for the id, 4 bytes for the value. public static bool TryReadFrame(ref ReadOnlySequence<byte> buffer, Http2Frame frame, uint maxFrameSize, out ReadOnlySequence<byte> framePayload) { framePayload = ReadOnlySequence<byte>.Empty; if (buffer.Length < HeaderLength) { return false; } var headerSlice = buffer.Slice(0, HeaderLength); var header = headerSlice.ToSpan(); var payloadLength = (int)Bitshifter.ReadUInt24BigEndian(header); if (payloadLength > maxFrameSize) { throw new Http2ConnectionErrorException(SharedStrings.FormatHttp2ErrorFrameOverLimit(payloadLength, maxFrameSize), Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionEndReason.MaxFrameLengthExceeded); } // Make sure the whole frame is buffered var frameLength = HeaderLength + payloadLength; if (buffer.Length < frameLength) { return false; } frame.PayloadLength = payloadLength; frame.Type = (Http2FrameType)header[TypeOffset]; frame.Flags = header[FlagsOffset]; frame.StreamId = (int)Bitshifter.ReadUInt31BigEndian(header.Slice(StreamIdOffset)); var extendedHeaderLength = ReadExtendedFields(frame, buffer); // The remaining payload minus the extra fields framePayload = buffer.Slice(HeaderLength + extendedHeaderLength, payloadLength - extendedHeaderLength); buffer = buffer.Slice(framePayload.End); return true; } private static int ReadExtendedFields(Http2Frame frame, in ReadOnlySequence<byte> readableBuffer) { // Copy in any extra fields for the given frame type var extendedHeaderLength = GetPayloadFieldsLength(frame); if (extendedHeaderLength > frame.PayloadLength) { throw new Http2ConnectionErrorException( SharedStrings.FormatHttp2ErrorUnexpectedFrameLength(frame.Type, expectedLength: extendedHeaderLength), Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionEndReason.InvalidFrameLength); } var extendedHeaders = readableBuffer.Slice(HeaderLength, extendedHeaderLength).ToSpan(); // Parse frame type specific fields switch (frame.Type) { /* +---------------+ |Pad Length? (8)| +---------------+-----------------------------------------------+ | Data (*) ... +---------------------------------------------------------------+ | Padding (*) ... +---------------------------------------------------------------+ */ case Http2FrameType.DATA: // Variable 0 or 1 frame.DataPadLength = frame.DataHasPadding ? extendedHeaders[0] : (byte)0; break; /* https://tools.ietf.org/html/rfc7540#section-6.2 +---------------+ |Pad Length? (8)| +-+-------------+-----------------------------------------------+ |E| Stream Dependency? (31) | +-+-------------+-----------------------------------------------+ | Weight? (8) | +-+-------------+-----------------------------------------------+ | Header Block Fragment (*) ... +---------------------------------------------------------------+ | Padding (*) ... +---------------------------------------------------------------+ */ case Http2FrameType.HEADERS: if (frame.HeadersHasPadding) { frame.HeadersPadLength = extendedHeaders[0]; extendedHeaders = extendedHeaders.Slice(1); } else { frame.HeadersPadLength = 0; } if (frame.HeadersHasPriority) { frame.HeadersStreamDependency = (int)Bitshifter.ReadUInt31BigEndian(extendedHeaders); frame.HeadersPriorityWeight = extendedHeaders.Slice(4)[0]; } else { frame.HeadersStreamDependency = 0; frame.HeadersPriorityWeight = 0; } break; /* https://tools.ietf.org/html/rfc7540#section-6.8 +-+-------------------------------------------------------------+ |R| Last-Stream-ID (31) | +-+-------------------------------------------------------------+ | Error Code (32) | +---------------------------------------------------------------+ | Additional Debug Data (*) | +---------------------------------------------------------------+ */ case Http2FrameType.GOAWAY: frame.GoAwayLastStreamId = (int)Bitshifter.ReadUInt31BigEndian(extendedHeaders); frame.GoAwayErrorCode = (Http2ErrorCode)BinaryPrimitives.ReadUInt32BigEndian(extendedHeaders.Slice(4)); break; /* https://tools.ietf.org/html/rfc7540#section-6.3 +-+-------------------------------------------------------------+ |E| Stream Dependency (31) | +-+-------------+-----------------------------------------------+ | Weight (8) | +-+-------------+ */ case Http2FrameType.PRIORITY: frame.PriorityStreamDependency = (int)Bitshifter.ReadUInt31BigEndian(extendedHeaders); frame.PriorityWeight = extendedHeaders.Slice(4)[0]; break; /* https://tools.ietf.org/html/rfc7540#section-6.4 +---------------------------------------------------------------+ | Error Code (32) | +---------------------------------------------------------------+ */ case Http2FrameType.RST_STREAM: frame.RstStreamErrorCode = (Http2ErrorCode)BinaryPrimitives.ReadUInt32BigEndian(extendedHeaders); break; /* https://tools.ietf.org/html/rfc7540#section-6.9 +-+-------------------------------------------------------------+ |R| Window Size Increment (31) | +-+-------------------------------------------------------------+ */ case Http2FrameType.WINDOW_UPDATE: frame.WindowUpdateSizeIncrement = (int)Bitshifter.ReadUInt31BigEndian(extendedHeaders); break; case Http2FrameType.PING: // Opaque payload 8 bytes long case Http2FrameType.SETTINGS: // Settings are general payload case Http2FrameType.CONTINUATION: // None case Http2FrameType.PUSH_PROMISE: // Not implemented frames are ignored at this phase default: return 0; } return extendedHeaderLength; } // The length in bytes of additional fields stored in the payload section. // This may be variable based on flags, but should be no more than 8 bytes. public static int GetPayloadFieldsLength(Http2Frame frame) { switch (frame.Type) { // TODO: Extract constants case Http2FrameType.DATA: // Variable 0 or 1 return frame.DataHasPadding ? 1 : 0; case Http2FrameType.HEADERS: return frame.HeadersPayloadOffset; // Variable 0 to 6 case Http2FrameType.GOAWAY: return 8; // Last stream id and error code. case Http2FrameType.PRIORITY: return 5; // Stream dependency and weight case Http2FrameType.RST_STREAM: return 4; // Error code case Http2FrameType.WINDOW_UPDATE: return 4; // Update size case Http2FrameType.PING: // 8 bytes of opaque data case Http2FrameType.SETTINGS: // Settings are general payload case Http2FrameType.CONTINUATION: // None case Http2FrameType.PUSH_PROMISE: // Not implemented frames are ignored at this phase default: return 0; } } public static IList<Http2PeerSetting> ReadSettings(in ReadOnlySequence<byte> payload) { var data = payload.ToSpan(); Debug.Assert(data.Length % SettingSize == 0, "Invalid settings payload length"); var settingsCount = data.Length / SettingSize; var settings = new Http2PeerSetting[settingsCount]; for (int i = 0; i < settings.Length; i++) { settings[i] = ReadSetting(data); data = data.Slice(SettingSize); } return settings; } private static Http2PeerSetting ReadSetting(ReadOnlySpan<byte> payload) { var id = (Http2SettingsParameter)BinaryPrimitives.ReadUInt16BigEndian(payload); var value = BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(2)); return new Http2PeerSetting(id, value); } }