| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | #nullable enable |
| | | 5 | | using System.Buffers; |
| | | 6 | | using System.Diagnostics; |
| | | 7 | | using System.Numerics; |
| | | 8 | | #if KESTREL |
| | | 9 | | using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; |
| | | 10 | | #endif |
| | | 11 | | |
| | | 12 | | namespace System.Net.Http.HPack |
| | | 13 | | { |
| | | 14 | | internal sealed class HPackDecoder |
| | | 15 | | { |
| | | 16 | | private enum State : byte |
| | | 17 | | { |
| | | 18 | | Ready, |
| | | 19 | | HeaderFieldIndex, |
| | | 20 | | HeaderNameIndex, |
| | | 21 | | HeaderNameLength, |
| | | 22 | | HeaderNameLengthContinue, |
| | | 23 | | HeaderName, |
| | | 24 | | HeaderValueLength, |
| | | 25 | | HeaderValueLengthContinue, |
| | | 26 | | HeaderValue, |
| | | 27 | | DynamicTableSizeUpdate |
| | | 28 | | } |
| | | 29 | | |
| | | 30 | | // https://datatracker.ietf.org/doc/html/rfc9113#name-defined-settings |
| | | 31 | | // Initial value for SETTINGS_HEADER_TABLE_SIZE is 4,096 octets. |
| | | 32 | | public const int DefaultHeaderTableSize = 4096; |
| | | 33 | | |
| | | 34 | | // This is the initial size. Buffers will be dynamically resized as needed. |
| | | 35 | | public const int DefaultStringOctetsSize = 32; |
| | | 36 | | |
| | | 37 | | public const int DefaultMaxHeadersLength = 64 * 1024; |
| | | 38 | | |
| | | 39 | | // http://httpwg.org/specs/rfc7541.html#rfc.section.6.1 |
| | | 40 | | // 0 1 2 3 4 5 6 7 |
| | | 41 | | // +---+---+---+---+---+---+---+---+ |
| | | 42 | | // | 1 | Index (7+) | |
| | | 43 | | // +---+---------------------------+ |
| | | 44 | | private const byte IndexedHeaderFieldMask = 0x80; |
| | | 45 | | |
| | | 46 | | // http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.1 |
| | | 47 | | // 0 1 2 3 4 5 6 7 |
| | | 48 | | // +---+---+---+---+---+---+---+---+ |
| | | 49 | | // | 0 | 1 | Index (6+) | |
| | | 50 | | // +---+---+-----------------------+ |
| | | 51 | | private const byte LiteralHeaderFieldWithIncrementalIndexingMask = 0xc0; |
| | | 52 | | |
| | | 53 | | // http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.2 |
| | | 54 | | // 0 1 2 3 4 5 6 7 |
| | | 55 | | // +---+---+---+---+---+---+---+---+ |
| | | 56 | | // | 0 | 0 | 0 | 0 | Index (4+) | |
| | | 57 | | // +---+---+-----------------------+ |
| | | 58 | | private const byte LiteralHeaderFieldWithoutIndexingMask = 0xf0; |
| | | 59 | | |
| | | 60 | | // http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.3 |
| | | 61 | | // 0 1 2 3 4 5 6 7 |
| | | 62 | | // +---+---+---+---+---+---+---+---+ |
| | | 63 | | // | 0 | 0 | 0 | 1 | Index (4+) | |
| | | 64 | | // +---+---+-----------------------+ |
| | | 65 | | private const byte LiteralHeaderFieldNeverIndexedMask = 0xf0; |
| | | 66 | | |
| | | 67 | | // http://httpwg.org/specs/rfc7541.html#rfc.section.6.3 |
| | | 68 | | // 0 1 2 3 4 5 6 7 |
| | | 69 | | // +---+---+---+---+---+---+---+---+ |
| | | 70 | | // | 0 | 0 | 1 | Max size (5+) | |
| | | 71 | | // +---+---------------------------+ |
| | | 72 | | private const byte DynamicTableSizeUpdateMask = 0xe0; |
| | | 73 | | |
| | | 74 | | // http://httpwg.org/specs/rfc7541.html#rfc.section.5.2 |
| | | 75 | | // 0 1 2 3 4 5 6 7 |
| | | 76 | | // +---+---+---+---+---+---+---+---+ |
| | | 77 | | // | H | String Length (7+) | |
| | | 78 | | // +---+---------------------------+ |
| | | 79 | | private const byte HuffmanMask = 0x80; |
| | | 80 | | |
| | | 81 | | private const int IndexedHeaderFieldPrefix = 7; |
| | | 82 | | private const int LiteralHeaderFieldWithIncrementalIndexingPrefix = 6; |
| | | 83 | | private const int LiteralHeaderFieldWithoutIndexingPrefix = 4; |
| | | 84 | | private const int LiteralHeaderFieldNeverIndexedPrefix = 4; |
| | | 85 | | private const int DynamicTableSizeUpdatePrefix = 5; |
| | | 86 | | private const int StringLengthPrefix = 7; |
| | | 87 | | |
| | | 88 | | private readonly int _maxDynamicTableSize; |
| | | 89 | | private readonly int _maxHeadersLength; |
| | | 90 | | private readonly DynamicTable _dynamicTable; |
| | | 91 | | private IntegerDecoder _integerDecoder; |
| | | 92 | | private byte[] _stringOctets; |
| | | 93 | | private byte[] _headerNameOctets; |
| | | 94 | | private byte[] _headerValueOctets; |
| | | 95 | | private (int start, int length)? _headerNameRange; |
| | | 96 | | private (int start, int length)? _headerValueRange; |
| | | 97 | | |
| | 0 | 98 | | private State _state = State.Ready; |
| | | 99 | | private byte[]? _headerName; |
| | | 100 | | private int _headerStaticIndex; |
| | | 101 | | private int _stringIndex; |
| | | 102 | | private int _stringLength; |
| | | 103 | | private int _headerNameLength; |
| | | 104 | | private int _headerValueLength; |
| | | 105 | | private bool _index; |
| | | 106 | | private bool _huffman; |
| | | 107 | | private bool _headersObserved; |
| | | 108 | | |
| | | 109 | | public HPackDecoder(int maxDynamicTableSize = DefaultHeaderTableSize, int maxHeadersLength = DefaultMaxHeadersLe |
| | 0 | 110 | | : this(maxDynamicTableSize, maxHeadersLength, new DynamicTable(maxDynamicTableSize)) |
| | 0 | 111 | | { |
| | 0 | 112 | | } |
| | | 113 | | |
| | | 114 | | // For testing. |
| | 0 | 115 | | internal HPackDecoder(int maxDynamicTableSize, int maxHeadersLength, DynamicTable dynamicTable) |
| | 0 | 116 | | { |
| | 0 | 117 | | _maxDynamicTableSize = maxDynamicTableSize; |
| | 0 | 118 | | _maxHeadersLength = maxHeadersLength; |
| | 0 | 119 | | _dynamicTable = dynamicTable; |
| | | 120 | | |
| | 0 | 121 | | _stringOctets = new byte[DefaultStringOctetsSize]; |
| | 0 | 122 | | _headerNameOctets = new byte[DefaultStringOctetsSize]; |
| | 0 | 123 | | _headerValueOctets = new byte[DefaultStringOctetsSize]; |
| | 0 | 124 | | } |
| | | 125 | | |
| | | 126 | | public void Decode(in ReadOnlySequence<byte> data, bool endHeaders, IHttpStreamHeadersHandler handler) |
| | | 127 | | { |
| | | 128 | | foreach (ReadOnlyMemory<byte> segment in data) |
| | | 129 | | { |
| | | 130 | | DecodeInternal(segment.Span, handler); |
| | | 131 | | } |
| | | 132 | | |
| | | 133 | | CheckIncompleteHeaderBlock(endHeaders); |
| | | 134 | | } |
| | | 135 | | |
| | | 136 | | public void Decode(ReadOnlySpan<byte> data, bool endHeaders, IHttpStreamHeadersHandler handler) |
| | 0 | 137 | | { |
| | 0 | 138 | | DecodeInternal(data, handler); |
| | 0 | 139 | | CheckIncompleteHeaderBlock(endHeaders); |
| | 0 | 140 | | } |
| | | 141 | | |
| | | 142 | | private void DecodeInternal(ReadOnlySpan<byte> data, IHttpStreamHeadersHandler handler) |
| | 0 | 143 | | { |
| | 0 | 144 | | int currentIndex = 0; |
| | | 145 | | |
| | | 146 | | do |
| | 0 | 147 | | { |
| | 0 | 148 | | switch (_state) |
| | | 149 | | { |
| | | 150 | | case State.Ready: |
| | 0 | 151 | | Parse(data, ref currentIndex, handler); |
| | 0 | 152 | | break; |
| | | 153 | | case State.HeaderFieldIndex: |
| | 0 | 154 | | ParseHeaderFieldIndex(data, ref currentIndex, handler); |
| | 0 | 155 | | break; |
| | | 156 | | case State.HeaderNameIndex: |
| | 0 | 157 | | ParseHeaderNameIndex(data, ref currentIndex, handler); |
| | 0 | 158 | | break; |
| | | 159 | | case State.HeaderNameLength: |
| | 0 | 160 | | ParseHeaderNameLength(data, ref currentIndex, handler); |
| | 0 | 161 | | break; |
| | | 162 | | case State.HeaderNameLengthContinue: |
| | 0 | 163 | | ParseHeaderNameLengthContinue(data, ref currentIndex, handler); |
| | 0 | 164 | | break; |
| | | 165 | | case State.HeaderName: |
| | 0 | 166 | | ParseHeaderName(data, ref currentIndex, handler); |
| | 0 | 167 | | break; |
| | | 168 | | case State.HeaderValueLength: |
| | 0 | 169 | | ParseHeaderValueLength(data, ref currentIndex, handler); |
| | 0 | 170 | | break; |
| | | 171 | | case State.HeaderValueLengthContinue: |
| | 0 | 172 | | ParseHeaderValueLengthContinue(data, ref currentIndex, handler); |
| | 0 | 173 | | break; |
| | | 174 | | case State.HeaderValue: |
| | 0 | 175 | | ParseHeaderValue(data, ref currentIndex, handler); |
| | 0 | 176 | | break; |
| | | 177 | | case State.DynamicTableSizeUpdate: |
| | 0 | 178 | | ParseDynamicTableSizeUpdate(data, ref currentIndex); |
| | 0 | 179 | | break; |
| | | 180 | | default: |
| | | 181 | | // Can't happen |
| | 0 | 182 | | Debug.Fail("HPACK decoder reach an invalid state"); |
| | | 183 | | throw new NotImplementedException(_state.ToString()); |
| | | 184 | | } |
| | 0 | 185 | | } |
| | | 186 | | // Parse methods each check the length. This check is to see whether there is still data available |
| | | 187 | | // and to continue parsing. |
| | 0 | 188 | | while (currentIndex < data.Length); |
| | | 189 | | |
| | | 190 | | // If a header range was set, but the value was not in the data, then copy the range |
| | | 191 | | // to the name buffer. Must copy because the data will be replaced and the range |
| | | 192 | | // will no longer be valid. |
| | 0 | 193 | | if (_headerNameRange != null) |
| | 0 | 194 | | { |
| | 0 | 195 | | EnsureStringCapacity(ref _headerNameOctets, _headerNameLength); |
| | 0 | 196 | | _headerName = _headerNameOctets; |
| | | 197 | | |
| | 0 | 198 | | ReadOnlySpan<byte> headerBytes = data.Slice(_headerNameRange.GetValueOrDefault().start, _headerNameRange |
| | 0 | 199 | | headerBytes.CopyTo(_headerName); |
| | 0 | 200 | | _headerNameRange = null; |
| | 0 | 201 | | } |
| | 0 | 202 | | } |
| | | 203 | | |
| | | 204 | | private void ParseDynamicTableSizeUpdate(ReadOnlySpan<byte> data, ref int currentIndex) |
| | 0 | 205 | | { |
| | 0 | 206 | | if (TryDecodeInteger(data, ref currentIndex, out int intResult)) |
| | 0 | 207 | | { |
| | 0 | 208 | | SetDynamicHeaderTableSize(intResult); |
| | 0 | 209 | | _state = State.Ready; |
| | 0 | 210 | | } |
| | 0 | 211 | | } |
| | | 212 | | |
| | | 213 | | private void ParseHeaderValueLength(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler han |
| | 0 | 214 | | { |
| | 0 | 215 | | if (currentIndex < data.Length) |
| | 0 | 216 | | { |
| | 0 | 217 | | byte b = data[currentIndex++]; |
| | | 218 | | |
| | 0 | 219 | | _huffman = IsHuffmanEncoded(b); |
| | | 220 | | |
| | 0 | 221 | | if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out int intResult)) |
| | 0 | 222 | | { |
| | 0 | 223 | | OnStringLength(intResult, nextState: State.HeaderValue); |
| | | 224 | | |
| | 0 | 225 | | if (intResult == 0) |
| | 0 | 226 | | { |
| | 0 | 227 | | OnString(nextState: State.Ready); |
| | 0 | 228 | | ProcessHeaderValue(data, handler); |
| | 0 | 229 | | } |
| | | 230 | | else |
| | 0 | 231 | | { |
| | 0 | 232 | | ParseHeaderValue(data, ref currentIndex, handler); |
| | 0 | 233 | | } |
| | 0 | 234 | | } |
| | | 235 | | else |
| | 0 | 236 | | { |
| | 0 | 237 | | _state = State.HeaderValueLengthContinue; |
| | 0 | 238 | | ParseHeaderValueLengthContinue(data, ref currentIndex, handler); |
| | 0 | 239 | | } |
| | 0 | 240 | | } |
| | 0 | 241 | | } |
| | | 242 | | |
| | | 243 | | private void ParseHeaderNameLengthContinue(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHand |
| | 0 | 244 | | { |
| | 0 | 245 | | if (TryDecodeInteger(data, ref currentIndex, out int intResult)) |
| | 0 | 246 | | { |
| | | 247 | | // IntegerDecoder disallows overlong encodings, where an integer is encoded with more bytes than is stri |
| | | 248 | | // 0 should always be represented by a single byte, so we shouldn't need to check for it in the continua |
| | 0 | 249 | | Debug.Assert(intResult != 0, "A header name length of 0 should never be encoded with a continuation byte |
| | | 250 | | |
| | 0 | 251 | | OnStringLength(intResult, nextState: State.HeaderName); |
| | 0 | 252 | | ParseHeaderName(data, ref currentIndex, handler); |
| | 0 | 253 | | } |
| | 0 | 254 | | } |
| | | 255 | | |
| | | 256 | | private void ParseHeaderValueLengthContinue(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHan |
| | 0 | 257 | | { |
| | 0 | 258 | | if (TryDecodeInteger(data, ref currentIndex, out int intResult)) |
| | 0 | 259 | | { |
| | | 260 | | // 0 should always be represented by a single byte, so we shouldn't need to check for it in the continua |
| | 0 | 261 | | Debug.Assert(intResult != 0, "A header value length of 0 should never be encoded with a continuation byt |
| | | 262 | | |
| | 0 | 263 | | OnStringLength(intResult, nextState: State.HeaderValue); |
| | 0 | 264 | | ParseHeaderValue(data, ref currentIndex, handler); |
| | 0 | 265 | | } |
| | 0 | 266 | | } |
| | | 267 | | |
| | | 268 | | private void ParseHeaderFieldIndex(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler hand |
| | 0 | 269 | | { |
| | 0 | 270 | | if (TryDecodeInteger(data, ref currentIndex, out int intResult)) |
| | 0 | 271 | | { |
| | 0 | 272 | | OnIndexedHeaderField(intResult, handler); |
| | 0 | 273 | | } |
| | 0 | 274 | | } |
| | | 275 | | |
| | | 276 | | private void ParseHeaderNameIndex(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler handl |
| | 0 | 277 | | { |
| | 0 | 278 | | if (TryDecodeInteger(data, ref currentIndex, out int intResult)) |
| | 0 | 279 | | { |
| | 0 | 280 | | OnIndexedHeaderName(intResult); |
| | 0 | 281 | | ParseHeaderValueLength(data, ref currentIndex, handler); |
| | 0 | 282 | | } |
| | 0 | 283 | | } |
| | | 284 | | |
| | | 285 | | private void ParseHeaderNameLength(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler hand |
| | 0 | 286 | | { |
| | 0 | 287 | | if (currentIndex < data.Length) |
| | 0 | 288 | | { |
| | 0 | 289 | | byte b = data[currentIndex++]; |
| | | 290 | | |
| | 0 | 291 | | _huffman = IsHuffmanEncoded(b); |
| | | 292 | | |
| | 0 | 293 | | if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out int intResult)) |
| | 0 | 294 | | { |
| | 0 | 295 | | if (intResult == 0) |
| | 0 | 296 | | { |
| | 0 | 297 | | throw new HPackDecodingException(SR.Format(SR.net_http_invalid_header_name, "")); |
| | | 298 | | } |
| | | 299 | | |
| | 0 | 300 | | OnStringLength(intResult, nextState: State.HeaderName); |
| | 0 | 301 | | ParseHeaderName(data, ref currentIndex, handler); |
| | 0 | 302 | | } |
| | | 303 | | else |
| | 0 | 304 | | { |
| | 0 | 305 | | _state = State.HeaderNameLengthContinue; |
| | 0 | 306 | | ParseHeaderNameLengthContinue(data, ref currentIndex, handler); |
| | 0 | 307 | | } |
| | 0 | 308 | | } |
| | 0 | 309 | | } |
| | | 310 | | |
| | | 311 | | private void Parse(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler handler) |
| | 0 | 312 | | { |
| | 0 | 313 | | if (currentIndex < data.Length) |
| | 0 | 314 | | { |
| | 0 | 315 | | Debug.Assert(_state == State.Ready, "Should be ready to parse a new header."); |
| | | 316 | | |
| | 0 | 317 | | byte b = data[currentIndex++]; |
| | | 318 | | |
| | 0 | 319 | | switch (BitOperations.LeadingZeroCount(b) - 24) // byte 'b' is extended to uint, so will have 24 extra 0 |
| | | 320 | | { |
| | | 321 | | case 0: // Indexed Header Field |
| | 0 | 322 | | { |
| | 0 | 323 | | _headersObserved = true; |
| | | 324 | | |
| | 0 | 325 | | int val = b & ~IndexedHeaderFieldMask; |
| | | 326 | | |
| | 0 | 327 | | if (_integerDecoder.BeginTryDecode((byte)val, IndexedHeaderFieldPrefix, out int intResult)) |
| | 0 | 328 | | { |
| | 0 | 329 | | OnIndexedHeaderField(intResult, handler); |
| | 0 | 330 | | } |
| | | 331 | | else |
| | 0 | 332 | | { |
| | 0 | 333 | | _state = State.HeaderFieldIndex; |
| | 0 | 334 | | ParseHeaderFieldIndex(data, ref currentIndex, handler); |
| | 0 | 335 | | } |
| | 0 | 336 | | break; |
| | | 337 | | } |
| | | 338 | | case 1: // Literal Header Field with Incremental Indexing |
| | 0 | 339 | | ParseLiteralHeaderField( |
| | 0 | 340 | | data, |
| | 0 | 341 | | ref currentIndex, |
| | 0 | 342 | | b, |
| | 0 | 343 | | LiteralHeaderFieldWithIncrementalIndexingMask, |
| | 0 | 344 | | LiteralHeaderFieldWithIncrementalIndexingPrefix, |
| | 0 | 345 | | index: true, |
| | 0 | 346 | | handler); |
| | 0 | 347 | | break; |
| | | 348 | | case 4: |
| | | 349 | | default: // Literal Header Field without Indexing |
| | 0 | 350 | | ParseLiteralHeaderField( |
| | 0 | 351 | | data, |
| | 0 | 352 | | ref currentIndex, |
| | 0 | 353 | | b, |
| | 0 | 354 | | LiteralHeaderFieldWithoutIndexingMask, |
| | 0 | 355 | | LiteralHeaderFieldWithoutIndexingPrefix, |
| | 0 | 356 | | index: false, |
| | 0 | 357 | | handler); |
| | 0 | 358 | | break; |
| | | 359 | | case 3: // Literal Header Field Never Indexed |
| | 0 | 360 | | ParseLiteralHeaderField( |
| | 0 | 361 | | data, |
| | 0 | 362 | | ref currentIndex, |
| | 0 | 363 | | b, |
| | 0 | 364 | | LiteralHeaderFieldNeverIndexedMask, |
| | 0 | 365 | | LiteralHeaderFieldNeverIndexedPrefix, |
| | 0 | 366 | | index: false, |
| | 0 | 367 | | handler); |
| | 0 | 368 | | break; |
| | | 369 | | case 2: // Dynamic Table Size Update |
| | 0 | 370 | | { |
| | | 371 | | // https://tools.ietf.org/html/rfc7541#section-4.2 |
| | | 372 | | // This dynamic table size |
| | | 373 | | // update MUST occur at the beginning of the first header block |
| | | 374 | | // following the change to the dynamic table size. |
| | 0 | 375 | | if (_headersObserved) |
| | 0 | 376 | | { |
| | 0 | 377 | | throw new HPackDecodingException(SR.net_http_hpack_late_dynamic_table_size_update); |
| | | 378 | | } |
| | | 379 | | |
| | 0 | 380 | | if (_integerDecoder.BeginTryDecode((byte)(b & ~DynamicTableSizeUpdateMask), DynamicTableSize |
| | 0 | 381 | | { |
| | 0 | 382 | | SetDynamicHeaderTableSize(intResult); |
| | 0 | 383 | | } |
| | | 384 | | else |
| | 0 | 385 | | { |
| | 0 | 386 | | _state = State.DynamicTableSizeUpdate; |
| | 0 | 387 | | ParseDynamicTableSizeUpdate(data, ref currentIndex); |
| | 0 | 388 | | } |
| | 0 | 389 | | break; |
| | | 390 | | } |
| | | 391 | | } |
| | 0 | 392 | | } |
| | 0 | 393 | | } |
| | | 394 | | |
| | | 395 | | private void ParseLiteralHeaderField(ReadOnlySpan<byte> data, ref int currentIndex, byte b, byte mask, byte inde |
| | 0 | 396 | | { |
| | 0 | 397 | | _headersObserved = true; |
| | | 398 | | |
| | 0 | 399 | | _index = index; |
| | 0 | 400 | | int val = b & ~mask; |
| | | 401 | | |
| | 0 | 402 | | if (val == 0) |
| | 0 | 403 | | { |
| | 0 | 404 | | _state = State.HeaderNameLength; |
| | 0 | 405 | | ParseHeaderNameLength(data, ref currentIndex, handler); |
| | 0 | 406 | | } |
| | | 407 | | else |
| | 0 | 408 | | { |
| | 0 | 409 | | if (_integerDecoder.BeginTryDecode((byte)val, indexPrefix, out int intResult)) |
| | 0 | 410 | | { |
| | 0 | 411 | | OnIndexedHeaderName(intResult); |
| | 0 | 412 | | ParseHeaderValueLength(data, ref currentIndex, handler); |
| | 0 | 413 | | } |
| | | 414 | | else |
| | 0 | 415 | | { |
| | 0 | 416 | | _state = State.HeaderNameIndex; |
| | 0 | 417 | | ParseHeaderNameIndex(data, ref currentIndex, handler); |
| | 0 | 418 | | } |
| | 0 | 419 | | } |
| | 0 | 420 | | } |
| | | 421 | | |
| | | 422 | | private void ParseHeaderName(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler handler) |
| | 0 | 423 | | { |
| | | 424 | | // Read remaining chars, up to the length of the current data |
| | 0 | 425 | | int count = Math.Min(_stringLength - _stringIndex, data.Length - currentIndex); |
| | | 426 | | |
| | | 427 | | // Check whether the whole string is available in the data and no decompression required. |
| | | 428 | | // If string is good then mark its range. |
| | | 429 | | // NOTE: it may need to be copied to buffer later the if value is not current data. |
| | 0 | 430 | | if (count == _stringLength && !_huffman) |
| | 0 | 431 | | { |
| | | 432 | | // Fast path. Store the range rather than copying. |
| | 0 | 433 | | _headerNameRange = (start: currentIndex, count); |
| | 0 | 434 | | _headerNameLength = _stringLength; |
| | 0 | 435 | | currentIndex += count; |
| | | 436 | | |
| | 0 | 437 | | _state = State.HeaderValueLength; |
| | 0 | 438 | | ParseHeaderValueLength(data, ref currentIndex, handler); |
| | 0 | 439 | | } |
| | 0 | 440 | | else if (count == 0) |
| | 0 | 441 | | { |
| | | 442 | | // no-op |
| | 0 | 443 | | } |
| | | 444 | | else |
| | 0 | 445 | | { |
| | | 446 | | // Copy string to temporary buffer. |
| | | 447 | | // _stringOctets was already |
| | 0 | 448 | | data.Slice(currentIndex, count).CopyTo(_stringOctets.AsSpan(_stringIndex)); |
| | 0 | 449 | | _stringIndex += count; |
| | 0 | 450 | | currentIndex += count; |
| | | 451 | | |
| | 0 | 452 | | if (_stringIndex == _stringLength) |
| | 0 | 453 | | { |
| | 0 | 454 | | OnString(nextState: State.HeaderValueLength); |
| | 0 | 455 | | ParseHeaderValueLength(data, ref currentIndex, handler); |
| | 0 | 456 | | } |
| | 0 | 457 | | } |
| | 0 | 458 | | } |
| | | 459 | | |
| | | 460 | | private void ParseHeaderValue(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler handler) |
| | 0 | 461 | | { |
| | | 462 | | // Read remaining chars, up to the length of the current data |
| | 0 | 463 | | int count = Math.Min(_stringLength - _stringIndex, data.Length - currentIndex); |
| | | 464 | | |
| | | 465 | | // Check whether the whole string is available in the data and no decompressed required. |
| | | 466 | | // If string is good then mark its range. |
| | 0 | 467 | | if (count == _stringLength && !_huffman) |
| | 0 | 468 | | { |
| | | 469 | | // Fast path. Store the range rather than copying. |
| | 0 | 470 | | _headerValueRange = (start: currentIndex, count); |
| | 0 | 471 | | currentIndex += count; |
| | | 472 | | |
| | 0 | 473 | | _state = State.Ready; |
| | 0 | 474 | | ProcessHeaderValue(data, handler); |
| | 0 | 475 | | } |
| | | 476 | | else |
| | 0 | 477 | | { |
| | | 478 | | // Copy string to temporary buffer. |
| | 0 | 479 | | data.Slice(currentIndex, count).CopyTo(_stringOctets.AsSpan(_stringIndex)); |
| | 0 | 480 | | _stringIndex += count; |
| | 0 | 481 | | currentIndex += count; |
| | | 482 | | |
| | 0 | 483 | | if (_stringIndex == _stringLength) |
| | 0 | 484 | | { |
| | 0 | 485 | | OnString(nextState: State.Ready); |
| | 0 | 486 | | ProcessHeaderValue(data, handler); |
| | 0 | 487 | | } |
| | 0 | 488 | | } |
| | 0 | 489 | | } |
| | | 490 | | |
| | | 491 | | private void CheckIncompleteHeaderBlock(bool endHeaders) |
| | 0 | 492 | | { |
| | 0 | 493 | | if (endHeaders) |
| | 0 | 494 | | { |
| | 0 | 495 | | if (_state != State.Ready) |
| | 0 | 496 | | { |
| | 0 | 497 | | throw new HPackDecodingException(SR.net_http_hpack_incomplete_header_block); |
| | | 498 | | } |
| | | 499 | | |
| | 0 | 500 | | _headersObserved = false; |
| | 0 | 501 | | } |
| | 0 | 502 | | } |
| | | 503 | | |
| | | 504 | | private void ProcessHeaderValue(ReadOnlySpan<byte> data, IHttpStreamHeadersHandler handler) |
| | 0 | 505 | | { |
| | 0 | 506 | | ReadOnlySpan<byte> headerValueSpan = _headerValueRange == null |
| | 0 | 507 | | ? _headerValueOctets.AsSpan(0, _headerValueLength) |
| | 0 | 508 | | : data.Slice(_headerValueRange.GetValueOrDefault().start, _headerValueRange.GetValueOrDefault().length); |
| | | 509 | | |
| | 0 | 510 | | if (_headerStaticIndex > 0) |
| | 0 | 511 | | { |
| | 0 | 512 | | handler.OnStaticIndexedHeader(_headerStaticIndex, headerValueSpan); |
| | | 513 | | |
| | 0 | 514 | | if (_index) |
| | 0 | 515 | | { |
| | 0 | 516 | | _dynamicTable.Insert(_headerStaticIndex, H2StaticTable.Get(_headerStaticIndex - 1).Name, headerValue |
| | 0 | 517 | | } |
| | 0 | 518 | | } |
| | | 519 | | else |
| | 0 | 520 | | { |
| | 0 | 521 | | ReadOnlySpan<byte> headerNameSpan = _headerNameRange == null |
| | 0 | 522 | | ? _headerName.AsSpan(0, _headerNameLength) |
| | 0 | 523 | | : data.Slice(_headerNameRange.GetValueOrDefault().start, _headerNameRange.GetValueOrDefault().length |
| | | 524 | | |
| | 0 | 525 | | handler.OnHeader(headerNameSpan, headerValueSpan); |
| | | 526 | | |
| | 0 | 527 | | if (_index) |
| | 0 | 528 | | { |
| | 0 | 529 | | _dynamicTable.Insert(headerNameSpan, headerValueSpan); |
| | 0 | 530 | | } |
| | 0 | 531 | | } |
| | | 532 | | |
| | 0 | 533 | | _headerStaticIndex = 0; |
| | 0 | 534 | | _headerNameRange = null; |
| | 0 | 535 | | _headerValueRange = null; |
| | 0 | 536 | | } |
| | | 537 | | |
| | | 538 | | public void CompleteDecode() |
| | 0 | 539 | | { |
| | 0 | 540 | | if (_state != State.Ready) |
| | 0 | 541 | | { |
| | | 542 | | // Incomplete header block |
| | 0 | 543 | | throw new HPackDecodingException(SR.net_http_hpack_unexpected_end); |
| | | 544 | | } |
| | 0 | 545 | | } |
| | | 546 | | |
| | | 547 | | private void OnIndexedHeaderField(int index, IHttpStreamHeadersHandler handler) |
| | 0 | 548 | | { |
| | 0 | 549 | | if (index <= H2StaticTable.Count) |
| | 0 | 550 | | { |
| | 0 | 551 | | handler.OnStaticIndexedHeader(index); |
| | 0 | 552 | | } |
| | | 553 | | else |
| | 0 | 554 | | { |
| | 0 | 555 | | ref readonly HeaderField header = ref GetDynamicHeader(index); |
| | 0 | 556 | | handler.OnDynamicIndexedHeader(header.StaticTableIndex, header.Name, header.Value); |
| | 0 | 557 | | } |
| | | 558 | | |
| | 0 | 559 | | _state = State.Ready; |
| | 0 | 560 | | } |
| | | 561 | | |
| | | 562 | | private void OnIndexedHeaderName(int index) |
| | 0 | 563 | | { |
| | 0 | 564 | | if (index <= H2StaticTable.Count) |
| | 0 | 565 | | { |
| | 0 | 566 | | _headerStaticIndex = index; |
| | 0 | 567 | | } |
| | | 568 | | else |
| | 0 | 569 | | { |
| | 0 | 570 | | _headerName = GetDynamicHeader(index).Name; |
| | 0 | 571 | | _headerNameLength = _headerName.Length; |
| | 0 | 572 | | } |
| | 0 | 573 | | _state = State.HeaderValueLength; |
| | 0 | 574 | | } |
| | | 575 | | |
| | | 576 | | private void OnStringLength(int length, State nextState) |
| | 0 | 577 | | { |
| | 0 | 578 | | if (length > _stringOctets.Length) |
| | 0 | 579 | | { |
| | 0 | 580 | | if (length > _maxHeadersLength) |
| | 0 | 581 | | { |
| | 0 | 582 | | throw new HPackDecodingException(SR.Format(SR.net_http_headers_exceeded_length, _maxHeadersLength)); |
| | | 583 | | } |
| | | 584 | | |
| | 0 | 585 | | _stringOctets = new byte[Math.Max(length, Math.Min(_stringOctets.Length * 2, _maxHeadersLength))]; |
| | 0 | 586 | | } |
| | | 587 | | |
| | 0 | 588 | | _stringLength = length; |
| | 0 | 589 | | _stringIndex = 0; |
| | 0 | 590 | | _state = nextState; |
| | 0 | 591 | | } |
| | | 592 | | |
| | | 593 | | private void OnString(State nextState) |
| | 0 | 594 | | { |
| | | 595 | | int Decode(ref byte[] dst) |
| | 0 | 596 | | { |
| | 0 | 597 | | if (_huffman) |
| | 0 | 598 | | { |
| | 0 | 599 | | return Huffman.Decode(new ReadOnlySpan<byte>(_stringOctets, 0, _stringLength), ref dst); |
| | | 600 | | } |
| | | 601 | | else |
| | 0 | 602 | | { |
| | 0 | 603 | | EnsureStringCapacity(ref dst); |
| | 0 | 604 | | Buffer.BlockCopy(_stringOctets, 0, dst, 0, _stringLength); |
| | 0 | 605 | | return _stringLength; |
| | | 606 | | } |
| | 0 | 607 | | } |
| | | 608 | | |
| | | 609 | | try |
| | 0 | 610 | | { |
| | 0 | 611 | | if (_state == State.HeaderName) |
| | 0 | 612 | | { |
| | 0 | 613 | | _headerNameLength = Decode(ref _headerNameOctets); |
| | 0 | 614 | | _headerName = _headerNameOctets; |
| | 0 | 615 | | } |
| | | 616 | | else |
| | 0 | 617 | | { |
| | 0 | 618 | | _headerValueLength = Decode(ref _headerValueOctets); |
| | 0 | 619 | | } |
| | 0 | 620 | | } |
| | 0 | 621 | | catch (HuffmanDecodingException ex) |
| | 0 | 622 | | { |
| | 0 | 623 | | throw new HPackDecodingException(SR.net_http_hpack_huffman_decode_failed, ex); |
| | | 624 | | } |
| | | 625 | | |
| | 0 | 626 | | _state = nextState; |
| | 0 | 627 | | } |
| | | 628 | | |
| | | 629 | | private void EnsureStringCapacity(ref byte[] dst, int stringLength = -1) |
| | 0 | 630 | | { |
| | 0 | 631 | | stringLength = stringLength >= 0 ? stringLength : _stringLength; |
| | 0 | 632 | | if (dst.Length < stringLength) |
| | 0 | 633 | | { |
| | 0 | 634 | | dst = new byte[Math.Max(stringLength, Math.Min(dst.Length * 2, _maxHeadersLength))]; |
| | 0 | 635 | | } |
| | 0 | 636 | | } |
| | | 637 | | |
| | | 638 | | private bool TryDecodeInteger(ReadOnlySpan<byte> data, ref int currentIndex, out int result) |
| | 0 | 639 | | { |
| | 0 | 640 | | for (; currentIndex < data.Length; currentIndex++) |
| | 0 | 641 | | { |
| | 0 | 642 | | if (_integerDecoder.TryDecode(data[currentIndex], out result)) |
| | 0 | 643 | | { |
| | 0 | 644 | | currentIndex++; |
| | 0 | 645 | | return true; |
| | | 646 | | } |
| | 0 | 647 | | } |
| | | 648 | | |
| | 0 | 649 | | result = default; |
| | 0 | 650 | | return false; |
| | 0 | 651 | | } |
| | | 652 | | |
| | | 653 | | private static bool IsHuffmanEncoded(byte b) |
| | 0 | 654 | | { |
| | 0 | 655 | | return (b & HuffmanMask) != 0; |
| | 0 | 656 | | } |
| | | 657 | | |
| | | 658 | | private ref readonly HeaderField GetDynamicHeader(int index) |
| | 0 | 659 | | { |
| | | 660 | | try |
| | 0 | 661 | | { |
| | 0 | 662 | | return ref _dynamicTable[index - H2StaticTable.Count - 1]; |
| | | 663 | | } |
| | 0 | 664 | | catch (IndexOutOfRangeException) |
| | 0 | 665 | | { |
| | | 666 | | // Header index out of range. |
| | 0 | 667 | | throw new HPackDecodingException(SR.Format(SR.net_http_hpack_invalid_index, index)); |
| | | 668 | | } |
| | 0 | 669 | | } |
| | | 670 | | |
| | | 671 | | private void SetDynamicHeaderTableSize(int size) |
| | 0 | 672 | | { |
| | 0 | 673 | | if (size > _maxDynamicTableSize) |
| | 0 | 674 | | { |
| | 0 | 675 | | throw new HPackDecodingException(SR.Format(SR.net_http_hpack_large_table_size_update, size, _maxDynamicT |
| | | 676 | | } |
| | | 677 | | |
| | 0 | 678 | | _dynamicTable.UpdateMaxSize(size); |
| | 0 | 679 | | } |
| | | 680 | | } |
| | | 681 | | } |