| | | 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.Diagnostics.CodeAnalysis; |
| | | 8 | | using System.Net.Http.HPack; |
| | | 9 | | using System.Numerics; |
| | | 10 | | |
| | | 11 | | #if KESTREL |
| | | 12 | | using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; |
| | | 13 | | #endif |
| | | 14 | | |
| | | 15 | | namespace System.Net.Http.QPack |
| | | 16 | | { |
| | | 17 | | internal sealed class QPackDecoder : IDisposable |
| | | 18 | | { |
| | | 19 | | private enum State |
| | | 20 | | { |
| | | 21 | | RequiredInsertCount, |
| | | 22 | | RequiredInsertCountContinue, |
| | | 23 | | Base, |
| | | 24 | | BaseContinue, |
| | | 25 | | CompressedHeaders, |
| | | 26 | | HeaderFieldIndex, |
| | | 27 | | HeaderNameIndex, |
| | | 28 | | HeaderNameLength, |
| | | 29 | | HeaderName, |
| | | 30 | | HeaderValueLength, |
| | | 31 | | HeaderValueLengthContinue, |
| | | 32 | | HeaderValue, |
| | | 33 | | PostBaseIndex, |
| | | 34 | | HeaderNameIndexPostBase |
| | | 35 | | } |
| | | 36 | | |
| | | 37 | | //0 1 2 3 4 5 6 7 |
| | | 38 | | //+---+---+---+---+---+---+---+---+ |
| | | 39 | | //| Required Insert Count(8+) | |
| | | 40 | | //+---+---------------------------+ |
| | | 41 | | //| S | Delta Base(7+) | |
| | | 42 | | //+---+---------------------------+ |
| | | 43 | | //| Compressed Headers ... |
| | | 44 | | private const int RequiredInsertCountPrefix = 8; |
| | | 45 | | private const int BaseMask = 0x80; |
| | | 46 | | private const int BasePrefix = 7; |
| | | 47 | | //+-------------------------------+ |
| | | 48 | | |
| | | 49 | | //https://tools.ietf.org/html/draft-ietf-quic-qpack-09#section-4.5.2 |
| | | 50 | | //0 1 2 3 4 5 6 7 |
| | | 51 | | //+---+---+---+---+---+---+---+---+ |
| | | 52 | | //| 1 | S | Index(6+) | |
| | | 53 | | //+---+---+-----------------------+ |
| | | 54 | | private const byte IndexedHeaderStaticMask = 0x40; |
| | | 55 | | private const byte IndexedHeaderStaticRepresentation = 0x40; |
| | | 56 | | private const byte IndexedHeaderFieldPrefixMask = 0x3F; |
| | | 57 | | private const int IndexedHeaderFieldPrefix = 6; |
| | | 58 | | |
| | | 59 | | //0 1 2 3 4 5 6 7 |
| | | 60 | | //+---+---+---+---+---+---+---+---+ |
| | | 61 | | //| 0 | 0 | 0 | 1 | Index(4+) | |
| | | 62 | | //+---+---+---+---+---------------+ |
| | | 63 | | private const byte PostBaseIndexMask = 0xF0; |
| | | 64 | | private const int PostBaseIndexPrefix = 4; |
| | | 65 | | |
| | | 66 | | //0 1 2 3 4 5 6 7 |
| | | 67 | | //+---+---+---+---+---+---+---+---+ |
| | | 68 | | //| 0 | 1 | N | S |Name Index(4+)| |
| | | 69 | | //+---+---+---+---+---------------+ |
| | | 70 | | //| H | Value Length(7+) | |
| | | 71 | | //+---+---------------------------+ |
| | | 72 | | //| Value String(Length bytes) | |
| | | 73 | | //+-------------------------------+ |
| | | 74 | | private const byte LiteralHeaderFieldStaticMask = 0x10; |
| | | 75 | | private const byte LiteralHeaderFieldPrefixMask = 0x0F; |
| | | 76 | | private const int LiteralHeaderFieldPrefix = 4; |
| | | 77 | | |
| | | 78 | | //0 1 2 3 4 5 6 7 |
| | | 79 | | //+---+---+---+---+---+---+---+---+ |
| | | 80 | | //| 0 | 0 | 0 | 0 | N |NameIdx(3+)| |
| | | 81 | | //+---+---+---+---+---+-----------+ |
| | | 82 | | //| H | Value Length(7+) | |
| | | 83 | | //+---+---------------------------+ |
| | | 84 | | //| Value String(Length bytes) | |
| | | 85 | | //+-------------------------------+ |
| | | 86 | | private const byte LiteralHeaderFieldPostBasePrefixMask = 0x07; |
| | | 87 | | private const int LiteralHeaderFieldPostBasePrefix = 3; |
| | | 88 | | |
| | | 89 | | //0 1 2 3 4 5 6 7 |
| | | 90 | | //+---+---+---+---+---+---+---+---+ |
| | | 91 | | //| 0 | 0 | 1 | N | H |NameLen(3+)| |
| | | 92 | | //+---+---+---+---+---+-----------+ |
| | | 93 | | //| Name String(Length bytes) | |
| | | 94 | | //+---+---------------------------+ |
| | | 95 | | //| H | Value Length(7+) | |
| | | 96 | | //+---+---------------------------+ |
| | | 97 | | //| Value String(Length bytes) | |
| | | 98 | | //+-------------------------------+ |
| | | 99 | | private const byte LiteralHeaderFieldWithoutNameReferenceHuffmanMask = 0x08; |
| | | 100 | | private const byte LiteralHeaderFieldWithoutNameReferencePrefixMask = 0x07; |
| | | 101 | | private const int LiteralHeaderFieldWithoutNameReferencePrefix = 3; |
| | | 102 | | |
| | | 103 | | private const int StringLengthPrefix = 7; |
| | | 104 | | private const byte HuffmanMask = 0x80; |
| | | 105 | | |
| | | 106 | | private const int HeaderStaticIndexUnset = -1; // Static index starts at 0 |
| | | 107 | | |
| | | 108 | | private readonly int _maxHeadersLength; |
| | 0 | 109 | | private State _state = State.RequiredInsertCount; |
| | | 110 | | |
| | | 111 | | // s is used for whatever s is in each field. This has multiple definition |
| | | 112 | | private bool _huffman; |
| | | 113 | | |
| | | 114 | | private byte[]? _headerName; |
| | | 115 | | private int _headerStaticIndex; |
| | | 116 | | private int _headerNameLength; |
| | | 117 | | private int _headerValueLength; |
| | | 118 | | private int _stringLength; |
| | | 119 | | private int _stringIndex; |
| | | 120 | | |
| | | 121 | | private IntegerDecoder _integerDecoder; |
| | | 122 | | private byte[]? _stringOctets; |
| | | 123 | | private byte[]? _headerNameOctets; |
| | | 124 | | private byte[]? _headerValueOctets; |
| | | 125 | | private (int start, int length)? _headerNameRange; |
| | | 126 | | private (int start, int length)? _headerValueRange; |
| | | 127 | | |
| | 0 | 128 | | private static ArrayPool<byte> Pool => ArrayPool<byte>.Shared; |
| | | 129 | | |
| | 0 | 130 | | public QPackDecoder(int maxHeadersLength) |
| | 0 | 131 | | { |
| | 0 | 132 | | _maxHeadersLength = maxHeadersLength; |
| | 0 | 133 | | _headerStaticIndex = HeaderStaticIndexUnset; |
| | 0 | 134 | | } |
| | | 135 | | |
| | | 136 | | public void Dispose() |
| | 0 | 137 | | { |
| | 0 | 138 | | if (_stringOctets != null) |
| | 0 | 139 | | { |
| | 0 | 140 | | Pool.Return(_stringOctets); |
| | 0 | 141 | | _stringOctets = null!; |
| | 0 | 142 | | } |
| | | 143 | | |
| | 0 | 144 | | if (_headerNameOctets != null) |
| | 0 | 145 | | { |
| | 0 | 146 | | Pool.Return(_headerNameOctets); |
| | 0 | 147 | | _headerNameOctets = null!; |
| | 0 | 148 | | } |
| | | 149 | | |
| | 0 | 150 | | if (_headerValueOctets != null) |
| | 0 | 151 | | { |
| | 0 | 152 | | Pool.Return(_headerValueOctets); |
| | 0 | 153 | | _headerValueOctets = null!; |
| | 0 | 154 | | } |
| | 0 | 155 | | } |
| | | 156 | | |
| | | 157 | | /// <summary> |
| | | 158 | | /// Reset the decoder state back to its initial value. Resetting state is required when reusing a decoder with m |
| | | 159 | | /// header frames. For example, decoding a response's headers and trailers. |
| | | 160 | | /// </summary> |
| | | 161 | | public void Reset() |
| | 0 | 162 | | { |
| | 0 | 163 | | _state = State.RequiredInsertCount; |
| | 0 | 164 | | } |
| | | 165 | | |
| | | 166 | | public void Decode(in ReadOnlySequence<byte> data, bool endHeaders, IHttpStreamHeadersHandler handler) |
| | | 167 | | { |
| | | 168 | | foreach (ReadOnlyMemory<byte> segment in data) |
| | | 169 | | { |
| | | 170 | | DecodeInternal(segment.Span, handler); |
| | | 171 | | } |
| | | 172 | | CheckIncompleteHeaderBlock(endHeaders); |
| | | 173 | | } |
| | | 174 | | |
| | | 175 | | public void Decode(ReadOnlySpan<byte> data, bool endHeaders, IHttpStreamHeadersHandler handler) |
| | 0 | 176 | | { |
| | 0 | 177 | | DecodeInternal(data, handler); |
| | 0 | 178 | | CheckIncompleteHeaderBlock(endHeaders); |
| | 0 | 179 | | } |
| | | 180 | | |
| | | 181 | | private void DecodeInternal(ReadOnlySpan<byte> data, IHttpStreamHeadersHandler handler) |
| | 0 | 182 | | { |
| | 0 | 183 | | int currentIndex = 0; |
| | | 184 | | |
| | | 185 | | do |
| | 0 | 186 | | { |
| | 0 | 187 | | switch (_state) |
| | | 188 | | { |
| | | 189 | | case State.RequiredInsertCount: |
| | 0 | 190 | | ParseRequiredInsertCount(data, ref currentIndex, handler); |
| | 0 | 191 | | break; |
| | | 192 | | case State.RequiredInsertCountContinue: |
| | 0 | 193 | | ParseRequiredInsertCountContinue(data, ref currentIndex, handler); |
| | 0 | 194 | | break; |
| | | 195 | | case State.Base: |
| | 0 | 196 | | ParseBase(data, ref currentIndex, handler); |
| | 0 | 197 | | break; |
| | | 198 | | case State.BaseContinue: |
| | 0 | 199 | | ParseBaseContinue(data, ref currentIndex, handler); |
| | 0 | 200 | | break; |
| | | 201 | | case State.CompressedHeaders: |
| | 0 | 202 | | ParseCompressedHeaders(data, ref currentIndex, handler); |
| | 0 | 203 | | break; |
| | | 204 | | case State.HeaderFieldIndex: |
| | 0 | 205 | | ParseHeaderFieldIndex(data, ref currentIndex, handler); |
| | 0 | 206 | | break; |
| | | 207 | | case State.HeaderNameIndex: |
| | 0 | 208 | | ParseHeaderNameIndex(data, ref currentIndex, handler); |
| | 0 | 209 | | break; |
| | | 210 | | case State.HeaderNameLength: |
| | 0 | 211 | | ParseHeaderNameLength(data, ref currentIndex, handler); |
| | 0 | 212 | | break; |
| | | 213 | | case State.HeaderName: |
| | 0 | 214 | | ParseHeaderName(data, ref currentIndex, handler); |
| | 0 | 215 | | break; |
| | | 216 | | case State.HeaderValueLength: |
| | 0 | 217 | | ParseHeaderValueLength(data, ref currentIndex, handler); |
| | 0 | 218 | | break; |
| | | 219 | | case State.HeaderValueLengthContinue: |
| | 0 | 220 | | ParseHeaderValueLengthContinue(data, ref currentIndex, handler); |
| | 0 | 221 | | break; |
| | | 222 | | case State.HeaderValue: |
| | 0 | 223 | | ParseHeaderValue(data, ref currentIndex, handler); |
| | 0 | 224 | | break; |
| | | 225 | | case State.PostBaseIndex: |
| | 0 | 226 | | ParsePostBaseIndex(data, ref currentIndex); |
| | 0 | 227 | | break; |
| | | 228 | | case State.HeaderNameIndexPostBase: |
| | 0 | 229 | | ParseHeaderNameIndexPostBase(data, ref currentIndex); |
| | 0 | 230 | | break; |
| | | 231 | | default: |
| | | 232 | | // Can't happen |
| | 0 | 233 | | Debug.Fail("QPACK decoder reach an invalid state"); |
| | | 234 | | throw new NotImplementedException(_state.ToString()); |
| | | 235 | | } |
| | 0 | 236 | | } |
| | | 237 | | // Parse methods each check the length. This check is to see whether there is still data available |
| | | 238 | | // and to continue parsing. |
| | 0 | 239 | | while (currentIndex < data.Length); |
| | | 240 | | |
| | | 241 | | // If a header range was set, but the value was not in the data, then copy the range |
| | | 242 | | // to the name buffer. Must copy because the data will be replaced and the range |
| | | 243 | | // will no longer be valid. |
| | 0 | 244 | | if (_headerNameRange != null) |
| | 0 | 245 | | { |
| | 0 | 246 | | EnsureStringCapacity(ref _headerNameOctets, _headerNameLength, existingLength: 0); |
| | 0 | 247 | | _headerName = _headerNameOctets; |
| | | 248 | | |
| | 0 | 249 | | ReadOnlySpan<byte> headerBytes = data.Slice(_headerNameRange.GetValueOrDefault().start, _headerNameRange |
| | 0 | 250 | | headerBytes.CopyTo(_headerName); |
| | 0 | 251 | | _headerNameRange = null; |
| | 0 | 252 | | } |
| | 0 | 253 | | } |
| | | 254 | | |
| | | 255 | | private void ParseHeaderNameIndexPostBase(ReadOnlySpan<byte> data, ref int currentIndex) |
| | 0 | 256 | | { |
| | 0 | 257 | | if (TryDecodeInteger(data, ref currentIndex, out int intResult)) |
| | 0 | 258 | | { |
| | 0 | 259 | | OnIndexedHeaderNamePostBase(intResult); |
| | 0 | 260 | | } |
| | 0 | 261 | | } |
| | | 262 | | |
| | | 263 | | private void ParsePostBaseIndex(ReadOnlySpan<byte> data, ref int currentIndex) |
| | 0 | 264 | | { |
| | 0 | 265 | | if (TryDecodeInteger(data, ref currentIndex, out _)) |
| | 0 | 266 | | { |
| | 0 | 267 | | OnPostBaseIndex(); |
| | 0 | 268 | | } |
| | 0 | 269 | | } |
| | | 270 | | |
| | | 271 | | private void ParseHeaderNameLength(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler hand |
| | 0 | 272 | | { |
| | 0 | 273 | | if (TryDecodeInteger(data, ref currentIndex, out int intResult)) |
| | 0 | 274 | | { |
| | 0 | 275 | | if (intResult == 0) |
| | 0 | 276 | | { |
| | 0 | 277 | | throw new QPackDecodingException(SR.Format(SR.net_http_invalid_header_name, "")); |
| | | 278 | | } |
| | 0 | 279 | | OnStringLength(intResult, nextState: State.HeaderName); |
| | 0 | 280 | | ParseHeaderName(data, ref currentIndex, handler); |
| | 0 | 281 | | } |
| | 0 | 282 | | } |
| | | 283 | | |
| | | 284 | | private void ParseHeaderName(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler handler) |
| | 0 | 285 | | { |
| | | 286 | | // Read remaining chars, up to the length of the current data |
| | 0 | 287 | | int count = Math.Min(_stringLength - _stringIndex, data.Length - currentIndex); |
| | | 288 | | |
| | | 289 | | // Check whether the whole string is available in the data and no decompression required. |
| | | 290 | | // If string is good then mark its range. |
| | | 291 | | // NOTE: it may need to be copied to buffer later the if value is not current data. |
| | 0 | 292 | | if (count == _stringLength && !_huffman) |
| | 0 | 293 | | { |
| | | 294 | | // Fast path. Store the range rather than copying. |
| | 0 | 295 | | _headerNameRange = (start: currentIndex, count); |
| | 0 | 296 | | _headerNameLength = _stringLength; |
| | 0 | 297 | | currentIndex += count; |
| | | 298 | | |
| | 0 | 299 | | _state = State.HeaderValueLength; |
| | 0 | 300 | | ParseHeaderValueLength(data, ref currentIndex, handler); |
| | 0 | 301 | | } |
| | 0 | 302 | | else if (count == 0) |
| | 0 | 303 | | { |
| | | 304 | | // no-op |
| | 0 | 305 | | } |
| | | 306 | | else |
| | 0 | 307 | | { |
| | | 308 | | // Copy string to temporary buffer. |
| | 0 | 309 | | EnsureStringCapacity(ref _stringOctets, _stringIndex + count, existingLength: _stringIndex); |
| | 0 | 310 | | data.Slice(currentIndex, count).CopyTo(_stringOctets.AsSpan(_stringIndex)); |
| | | 311 | | |
| | 0 | 312 | | _stringIndex += count; |
| | 0 | 313 | | currentIndex += count; |
| | | 314 | | |
| | 0 | 315 | | if (_stringIndex == _stringLength) |
| | 0 | 316 | | { |
| | 0 | 317 | | OnString(nextState: State.HeaderValueLength); |
| | 0 | 318 | | ParseHeaderValueLength(data, ref currentIndex, handler); |
| | 0 | 319 | | } |
| | 0 | 320 | | } |
| | 0 | 321 | | } |
| | | 322 | | |
| | | 323 | | private void ParseHeaderFieldIndex(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler hand |
| | 0 | 324 | | { |
| | 0 | 325 | | if (TryDecodeInteger(data, ref currentIndex, out int intResult)) |
| | 0 | 326 | | { |
| | 0 | 327 | | OnIndexedHeaderField(intResult, handler); |
| | 0 | 328 | | } |
| | 0 | 329 | | } |
| | | 330 | | |
| | | 331 | | private void ParseHeaderNameIndex(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler handl |
| | 0 | 332 | | { |
| | 0 | 333 | | if (TryDecodeInteger(data, ref currentIndex, out int intResult)) |
| | 0 | 334 | | { |
| | 0 | 335 | | OnIndexedHeaderName(intResult); |
| | 0 | 336 | | ParseHeaderValueLength(data, ref currentIndex, handler); |
| | 0 | 337 | | } |
| | 0 | 338 | | } |
| | | 339 | | |
| | | 340 | | private void ParseHeaderValueLength(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler han |
| | 0 | 341 | | { |
| | 0 | 342 | | if (currentIndex < data.Length) |
| | 0 | 343 | | { |
| | 0 | 344 | | byte b = data[currentIndex++]; |
| | | 345 | | |
| | 0 | 346 | | _huffman = IsHuffmanEncoded(b); |
| | | 347 | | |
| | 0 | 348 | | if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out int intResult)) |
| | 0 | 349 | | { |
| | 0 | 350 | | OnStringLength(intResult, nextState: State.HeaderValue); |
| | | 351 | | |
| | 0 | 352 | | if (intResult == 0) |
| | 0 | 353 | | { |
| | 0 | 354 | | _state = State.CompressedHeaders; |
| | 0 | 355 | | ProcessHeaderValue(data, handler); |
| | 0 | 356 | | } |
| | | 357 | | else |
| | 0 | 358 | | { |
| | 0 | 359 | | ParseHeaderValue(data, ref currentIndex, handler); |
| | 0 | 360 | | } |
| | 0 | 361 | | } |
| | | 362 | | else |
| | 0 | 363 | | { |
| | 0 | 364 | | _state = State.HeaderValueLengthContinue; |
| | 0 | 365 | | ParseHeaderValueLengthContinue(data, ref currentIndex, handler); |
| | 0 | 366 | | } |
| | 0 | 367 | | } |
| | 0 | 368 | | } |
| | | 369 | | |
| | | 370 | | private void ParseHeaderValue(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler handler) |
| | 0 | 371 | | { |
| | | 372 | | // Read remaining chars, up to the length of the current data |
| | 0 | 373 | | int count = Math.Min(_stringLength - _stringIndex, data.Length - currentIndex); |
| | | 374 | | |
| | | 375 | | // Check whether the whole string is available in the data and no decompressed required. |
| | | 376 | | // If string is good then mark its range. |
| | 0 | 377 | | if (count == _stringLength && !_huffman) |
| | 0 | 378 | | { |
| | | 379 | | // Fast path. Store the range rather than copying. |
| | 0 | 380 | | _headerValueRange = (start: currentIndex, count); |
| | 0 | 381 | | currentIndex += count; |
| | | 382 | | |
| | 0 | 383 | | _state = State.CompressedHeaders; |
| | 0 | 384 | | ProcessHeaderValue(data, handler); |
| | 0 | 385 | | } |
| | 0 | 386 | | else if (count == 0) |
| | 0 | 387 | | { |
| | | 388 | | // no-op |
| | 0 | 389 | | } |
| | | 390 | | else |
| | 0 | 391 | | { |
| | | 392 | | // Copy string to temporary buffer. |
| | 0 | 393 | | EnsureStringCapacity(ref _stringOctets, _stringIndex + count, existingLength: _stringIndex); |
| | 0 | 394 | | data.Slice(currentIndex, count).CopyTo(_stringOctets.AsSpan(_stringIndex)); |
| | | 395 | | |
| | 0 | 396 | | _stringIndex += count; |
| | 0 | 397 | | currentIndex += count; |
| | | 398 | | |
| | 0 | 399 | | if (_stringIndex == _stringLength) |
| | 0 | 400 | | { |
| | 0 | 401 | | OnString(nextState: State.CompressedHeaders); |
| | 0 | 402 | | ProcessHeaderValue(data, handler); |
| | 0 | 403 | | } |
| | 0 | 404 | | } |
| | 0 | 405 | | } |
| | | 406 | | |
| | | 407 | | private void ParseHeaderValueLengthContinue(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHan |
| | 0 | 408 | | { |
| | 0 | 409 | | if (TryDecodeInteger(data, ref currentIndex, out int intResult)) |
| | 0 | 410 | | { |
| | 0 | 411 | | if (intResult == 0) |
| | 0 | 412 | | { |
| | 0 | 413 | | _state = State.CompressedHeaders; |
| | 0 | 414 | | ProcessHeaderValue(data, handler); |
| | 0 | 415 | | } |
| | | 416 | | else |
| | 0 | 417 | | { |
| | 0 | 418 | | OnStringLength(intResult, nextState: State.HeaderValue); |
| | 0 | 419 | | ParseHeaderValue(data, ref currentIndex, handler); |
| | 0 | 420 | | } |
| | 0 | 421 | | } |
| | 0 | 422 | | } |
| | | 423 | | |
| | | 424 | | private void ParseCompressedHeaders(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler han |
| | 0 | 425 | | { |
| | 0 | 426 | | if (currentIndex < data.Length) |
| | 0 | 427 | | { |
| | 0 | 428 | | Debug.Assert(_state == State.CompressedHeaders, "Should be ready to parse a new header."); |
| | | 429 | | |
| | 0 | 430 | | byte b = data[currentIndex++]; |
| | | 431 | | int prefixInt; |
| | | 432 | | int intResult; |
| | | 433 | | |
| | 0 | 434 | | switch (BitOperations.LeadingZeroCount(b) - 24) // byte 'b' is extended to uint, so will have 24 extra 0 |
| | | 435 | | { |
| | | 436 | | case 0: // Indexed Header Field |
| | 0 | 437 | | prefixInt = IndexedHeaderFieldPrefixMask & b; |
| | | 438 | | |
| | 0 | 439 | | bool useStaticTable = (b & IndexedHeaderStaticMask) == IndexedHeaderStaticRepresentation; |
| | | 440 | | |
| | 0 | 441 | | if (!useStaticTable) |
| | 0 | 442 | | { |
| | 0 | 443 | | ThrowDynamicTableNotSupported(); |
| | 0 | 444 | | } |
| | | 445 | | |
| | 0 | 446 | | if (_integerDecoder.BeginTryDecode((byte)prefixInt, IndexedHeaderFieldPrefix, out intResult)) |
| | 0 | 447 | | { |
| | 0 | 448 | | OnIndexedHeaderField(intResult, handler); |
| | 0 | 449 | | } |
| | | 450 | | else |
| | 0 | 451 | | { |
| | 0 | 452 | | _state = State.HeaderFieldIndex; |
| | 0 | 453 | | ParseHeaderFieldIndex(data, ref currentIndex, handler); |
| | 0 | 454 | | } |
| | 0 | 455 | | break; |
| | | 456 | | case 1: // Literal Header Field With Name Reference |
| | 0 | 457 | | useStaticTable = (LiteralHeaderFieldStaticMask & b) == LiteralHeaderFieldStaticMask; |
| | | 458 | | |
| | 0 | 459 | | if (!useStaticTable) |
| | 0 | 460 | | { |
| | 0 | 461 | | ThrowDynamicTableNotSupported(); |
| | 0 | 462 | | } |
| | | 463 | | |
| | 0 | 464 | | prefixInt = b & LiteralHeaderFieldPrefixMask; |
| | 0 | 465 | | if (_integerDecoder.BeginTryDecode((byte)prefixInt, LiteralHeaderFieldPrefix, out intResult)) |
| | 0 | 466 | | { |
| | 0 | 467 | | OnIndexedHeaderName(intResult); |
| | 0 | 468 | | ParseHeaderValueLength(data, ref currentIndex, handler); |
| | 0 | 469 | | } |
| | | 470 | | else |
| | 0 | 471 | | { |
| | 0 | 472 | | _state = State.HeaderNameIndex; |
| | 0 | 473 | | ParseHeaderNameIndex(data, ref currentIndex, handler); |
| | 0 | 474 | | } |
| | 0 | 475 | | break; |
| | | 476 | | case 2: // Literal Header Field Without Name Reference |
| | 0 | 477 | | _huffman = (b & LiteralHeaderFieldWithoutNameReferenceHuffmanMask) != 0; |
| | 0 | 478 | | prefixInt = b & LiteralHeaderFieldWithoutNameReferencePrefixMask; |
| | | 479 | | |
| | 0 | 480 | | if (_integerDecoder.BeginTryDecode((byte)prefixInt, LiteralHeaderFieldWithoutNameReferencePrefix |
| | 0 | 481 | | { |
| | 0 | 482 | | if (intResult == 0) |
| | 0 | 483 | | { |
| | 0 | 484 | | throw new QPackDecodingException(SR.Format(SR.net_http_invalid_header_name, "")); |
| | | 485 | | } |
| | 0 | 486 | | OnStringLength(intResult, State.HeaderName); |
| | 0 | 487 | | ParseHeaderName(data, ref currentIndex, handler); |
| | 0 | 488 | | } |
| | | 489 | | else |
| | 0 | 490 | | { |
| | 0 | 491 | | _state = State.HeaderNameLength; |
| | 0 | 492 | | ParseHeaderNameLength(data, ref currentIndex, handler); |
| | 0 | 493 | | } |
| | 0 | 494 | | break; |
| | | 495 | | case 3: // Indexed Header Field With Post-Base Index |
| | 0 | 496 | | prefixInt = ~PostBaseIndexMask & b; |
| | 0 | 497 | | if (_integerDecoder.BeginTryDecode((byte)prefixInt, PostBaseIndexPrefix, out _)) |
| | 0 | 498 | | { |
| | 0 | 499 | | OnPostBaseIndex(); |
| | 0 | 500 | | } |
| | | 501 | | else |
| | 0 | 502 | | { |
| | 0 | 503 | | _state = State.PostBaseIndex; |
| | 0 | 504 | | ParsePostBaseIndex(data, ref currentIndex); |
| | 0 | 505 | | } |
| | 0 | 506 | | break; |
| | | 507 | | default: // Literal Header Field With Post-Base Name Reference (at least 4 zeroes, maybe more) |
| | 0 | 508 | | prefixInt = b & LiteralHeaderFieldPostBasePrefixMask; |
| | 0 | 509 | | if (_integerDecoder.BeginTryDecode((byte)prefixInt, LiteralHeaderFieldPostBasePrefix, out intRes |
| | 0 | 510 | | { |
| | 0 | 511 | | OnIndexedHeaderNamePostBase(intResult); |
| | 0 | 512 | | } |
| | | 513 | | else |
| | 0 | 514 | | { |
| | 0 | 515 | | _state = State.HeaderNameIndexPostBase; |
| | 0 | 516 | | ParseHeaderNameIndexPostBase(data, ref currentIndex); |
| | 0 | 517 | | } |
| | 0 | 518 | | break; |
| | | 519 | | } |
| | 0 | 520 | | } |
| | 0 | 521 | | } |
| | | 522 | | |
| | | 523 | | private void ParseRequiredInsertCountContinue(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersH |
| | 0 | 524 | | { |
| | 0 | 525 | | if (TryDecodeInteger(data, ref currentIndex, out int intResult)) |
| | 0 | 526 | | { |
| | 0 | 527 | | OnRequiredInsertCount(intResult); |
| | 0 | 528 | | ParseBase(data, ref currentIndex, handler); |
| | 0 | 529 | | } |
| | 0 | 530 | | } |
| | | 531 | | |
| | | 532 | | private void ParseBase(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler handler) |
| | 0 | 533 | | { |
| | 0 | 534 | | if (currentIndex < data.Length) |
| | 0 | 535 | | { |
| | 0 | 536 | | byte b = data[currentIndex++]; |
| | 0 | 537 | | int prefixInt = ~BaseMask & b; |
| | | 538 | | |
| | 0 | 539 | | if (_integerDecoder.BeginTryDecode((byte)prefixInt, BasePrefix, out int intResult)) |
| | 0 | 540 | | { |
| | 0 | 541 | | OnBase(intResult); |
| | 0 | 542 | | ParseCompressedHeaders(data, ref currentIndex, handler); |
| | 0 | 543 | | } |
| | | 544 | | else |
| | 0 | 545 | | { |
| | 0 | 546 | | _state = State.BaseContinue; |
| | 0 | 547 | | ParseBaseContinue(data, ref currentIndex, handler); |
| | 0 | 548 | | } |
| | 0 | 549 | | } |
| | 0 | 550 | | } |
| | | 551 | | |
| | | 552 | | private void ParseBaseContinue(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler handler) |
| | 0 | 553 | | { |
| | 0 | 554 | | if (TryDecodeInteger(data, ref currentIndex, out int intResult)) |
| | 0 | 555 | | { |
| | 0 | 556 | | OnBase(intResult); |
| | 0 | 557 | | ParseCompressedHeaders(data, ref currentIndex, handler); |
| | 0 | 558 | | } |
| | 0 | 559 | | } |
| | | 560 | | |
| | | 561 | | private void ParseRequiredInsertCount(ReadOnlySpan<byte> data, ref int currentIndex, IHttpStreamHeadersHandler h |
| | 0 | 562 | | { |
| | 0 | 563 | | if (currentIndex < data.Length) |
| | 0 | 564 | | { |
| | 0 | 565 | | byte b = data[currentIndex++]; |
| | | 566 | | |
| | 0 | 567 | | if (_integerDecoder.BeginTryDecode(b, RequiredInsertCountPrefix, out int intResult)) |
| | 0 | 568 | | { |
| | 0 | 569 | | OnRequiredInsertCount(intResult); |
| | 0 | 570 | | ParseBase(data, ref currentIndex, handler); |
| | 0 | 571 | | } |
| | | 572 | | else |
| | 0 | 573 | | { |
| | 0 | 574 | | _state = State.RequiredInsertCountContinue; |
| | 0 | 575 | | ParseRequiredInsertCountContinue(data, ref currentIndex, handler); |
| | 0 | 576 | | } |
| | 0 | 577 | | } |
| | 0 | 578 | | } |
| | | 579 | | |
| | | 580 | | private void CheckIncompleteHeaderBlock(bool endHeaders) |
| | 0 | 581 | | { |
| | 0 | 582 | | if (endHeaders) |
| | 0 | 583 | | { |
| | 0 | 584 | | if (_state != State.CompressedHeaders) |
| | 0 | 585 | | { |
| | 0 | 586 | | throw new QPackDecodingException(SR.net_http_hpack_incomplete_header_block); |
| | | 587 | | } |
| | 0 | 588 | | } |
| | 0 | 589 | | } |
| | | 590 | | |
| | | 591 | | private void ProcessHeaderValue(ReadOnlySpan<byte> data, IHttpStreamHeadersHandler handler) |
| | 0 | 592 | | { |
| | 0 | 593 | | ReadOnlySpan<byte> headerValueSpan = _headerValueRange == null |
| | 0 | 594 | | ? _headerValueOctets.AsSpan(0, _headerValueLength) |
| | 0 | 595 | | : data.Slice(_headerValueRange.GetValueOrDefault().start, _headerValueRange.GetValueOrDefault().length); |
| | | 596 | | |
| | 0 | 597 | | if (_headerStaticIndex != HeaderStaticIndexUnset) |
| | 0 | 598 | | { |
| | 0 | 599 | | handler.OnStaticIndexedHeader(_headerStaticIndex, headerValueSpan); |
| | 0 | 600 | | } |
| | | 601 | | else |
| | 0 | 602 | | { |
| | 0 | 603 | | ReadOnlySpan<byte> headerNameSpan = _headerNameRange == null |
| | 0 | 604 | | ? _headerName.AsSpan(0, _headerNameLength) |
| | 0 | 605 | | : data.Slice(_headerNameRange.GetValueOrDefault().start, _headerNameRange.GetValueOrDefault().length |
| | | 606 | | |
| | 0 | 607 | | handler.OnHeader(headerNameSpan, headerValueSpan); |
| | 0 | 608 | | } |
| | | 609 | | |
| | 0 | 610 | | _headerStaticIndex = HeaderStaticIndexUnset; |
| | 0 | 611 | | _headerNameRange = null; |
| | 0 | 612 | | _headerNameLength = 0; |
| | 0 | 613 | | _headerValueRange = null; |
| | 0 | 614 | | _headerValueLength = 0; |
| | 0 | 615 | | } |
| | | 616 | | |
| | | 617 | | private void OnStringLength(int length, State nextState) |
| | 0 | 618 | | { |
| | 0 | 619 | | if (length > _maxHeadersLength) |
| | 0 | 620 | | { |
| | 0 | 621 | | throw new QPackDecodingException(SR.Format(SR.net_http_headers_exceeded_length, _maxHeadersLength)); |
| | | 622 | | } |
| | | 623 | | |
| | 0 | 624 | | _stringLength = length; |
| | 0 | 625 | | _stringIndex = 0; |
| | 0 | 626 | | _state = nextState; |
| | 0 | 627 | | } |
| | | 628 | | |
| | | 629 | | private void OnString(State nextState) |
| | 0 | 630 | | { |
| | | 631 | | int Decode(ref byte[]? dst) |
| | 0 | 632 | | { |
| | 0 | 633 | | EnsureStringCapacity(ref dst, _stringLength, existingLength: 0); |
| | | 634 | | |
| | 0 | 635 | | if (_huffman) |
| | 0 | 636 | | { |
| | 0 | 637 | | return Huffman.Decode(new ReadOnlySpan<byte>(_stringOctets, 0, _stringLength), ref dst); |
| | | 638 | | } |
| | | 639 | | else |
| | 0 | 640 | | { |
| | 0 | 641 | | Buffer.BlockCopy(_stringOctets, 0, dst, 0, _stringLength); |
| | 0 | 642 | | return _stringLength; |
| | | 643 | | } |
| | 0 | 644 | | } |
| | | 645 | | |
| | 0 | 646 | | Debug.Assert(_stringOctets != null, "String buffer should have a value."); |
| | | 647 | | |
| | | 648 | | try |
| | 0 | 649 | | { |
| | 0 | 650 | | if (_state == State.HeaderName) |
| | 0 | 651 | | { |
| | 0 | 652 | | _headerNameLength = Decode(ref _headerNameOctets); |
| | 0 | 653 | | _headerName = _headerNameOctets; |
| | 0 | 654 | | } |
| | | 655 | | else |
| | 0 | 656 | | { |
| | 0 | 657 | | _headerValueLength = Decode(ref _headerValueOctets); |
| | 0 | 658 | | } |
| | 0 | 659 | | } |
| | 0 | 660 | | catch (HuffmanDecodingException ex) |
| | 0 | 661 | | { |
| | 0 | 662 | | throw new QPackDecodingException(SR.net_http_hpack_huffman_decode_failed, ex); |
| | | 663 | | } |
| | | 664 | | |
| | 0 | 665 | | _state = nextState; |
| | 0 | 666 | | } |
| | | 667 | | |
| | | 668 | | private static void EnsureStringCapacity([NotNull] ref byte[]? buffer, int requiredLength, int existingLength) |
| | 0 | 669 | | { |
| | 0 | 670 | | if (buffer == null) |
| | 0 | 671 | | { |
| | 0 | 672 | | buffer = Pool.Rent(requiredLength); |
| | 0 | 673 | | } |
| | 0 | 674 | | else if (buffer.Length < requiredLength) |
| | 0 | 675 | | { |
| | 0 | 676 | | byte[] newBuffer = Pool.Rent(requiredLength); |
| | 0 | 677 | | if (existingLength > 0) |
| | 0 | 678 | | { |
| | 0 | 679 | | buffer.AsMemory(0, existingLength).CopyTo(newBuffer); |
| | 0 | 680 | | } |
| | | 681 | | |
| | 0 | 682 | | Pool.Return(buffer); |
| | 0 | 683 | | buffer = newBuffer; |
| | 0 | 684 | | } |
| | 0 | 685 | | } |
| | | 686 | | |
| | | 687 | | private bool TryDecodeInteger(ReadOnlySpan<byte> data, ref int currentIndex, out int result) |
| | 0 | 688 | | { |
| | 0 | 689 | | for (; currentIndex < data.Length; currentIndex++) |
| | 0 | 690 | | { |
| | 0 | 691 | | if (_integerDecoder.TryDecode(data[currentIndex], out result)) |
| | 0 | 692 | | { |
| | 0 | 693 | | currentIndex++; |
| | 0 | 694 | | return true; |
| | | 695 | | } |
| | 0 | 696 | | } |
| | | 697 | | |
| | 0 | 698 | | result = default; |
| | 0 | 699 | | return false; |
| | 0 | 700 | | } |
| | | 701 | | |
| | | 702 | | private static bool IsHuffmanEncoded(byte b) |
| | 0 | 703 | | { |
| | 0 | 704 | | return (b & HuffmanMask) != 0; |
| | 0 | 705 | | } |
| | | 706 | | |
| | | 707 | | private void OnIndexedHeaderName(int index) |
| | 0 | 708 | | { |
| | 0 | 709 | | _headerStaticIndex = index; |
| | 0 | 710 | | _state = State.HeaderValueLength; |
| | 0 | 711 | | } |
| | | 712 | | |
| | | 713 | | private static void OnIndexedHeaderNamePostBase(int _ /*index*/) |
| | 0 | 714 | | { |
| | 0 | 715 | | ThrowDynamicTableNotSupported(); |
| | | 716 | | // TODO update with postbase index |
| | | 717 | | // _index = index; |
| | | 718 | | // _state = State.HeaderValueLength; |
| | 0 | 719 | | } |
| | | 720 | | |
| | | 721 | | private static void OnPostBaseIndex() |
| | 0 | 722 | | { |
| | 0 | 723 | | ThrowDynamicTableNotSupported(); |
| | | 724 | | // TODO |
| | | 725 | | // _state = State.CompressedHeaders; |
| | 0 | 726 | | } |
| | | 727 | | |
| | | 728 | | private void OnBase(int deltaBase) |
| | 0 | 729 | | { |
| | 0 | 730 | | if (deltaBase != 0) |
| | 0 | 731 | | { |
| | 0 | 732 | | ThrowDynamicTableNotSupported(); |
| | 0 | 733 | | } |
| | 0 | 734 | | _state = State.CompressedHeaders; |
| | 0 | 735 | | } |
| | | 736 | | |
| | | 737 | | private void OnRequiredInsertCount(int requiredInsertCount) |
| | 0 | 738 | | { |
| | 0 | 739 | | if (requiredInsertCount != 0) |
| | 0 | 740 | | { |
| | 0 | 741 | | ThrowDynamicTableNotSupported(); |
| | 0 | 742 | | } |
| | 0 | 743 | | _state = State.Base; |
| | 0 | 744 | | } |
| | | 745 | | |
| | | 746 | | private void OnIndexedHeaderField(int index, IHttpStreamHeadersHandler handler) |
| | 0 | 747 | | { |
| | 0 | 748 | | handler.OnStaticIndexedHeader(index); |
| | 0 | 749 | | _state = State.CompressedHeaders; |
| | 0 | 750 | | } |
| | | 751 | | |
| | | 752 | | private static void ThrowDynamicTableNotSupported() |
| | 0 | 753 | | { |
| | 0 | 754 | | throw new QPackDecodingException(SR.net_http_qpack_no_dynamic_table); |
| | | 755 | | } |
| | | 756 | | } |
| | | 757 | | } |