public bool TakeMessageHeaders(ref SequenceReader <byte> reader, bool trailers) { // Make sure the buffer is limited if (reader.Remaining > _remainingRequestHeadersBytesAllowed) { // Input oversize, cap amount checked return(TrimAndTakeMessageHeaders(ref reader, trailers)); } var alreadyConsumed = reader.Consumed; try { var result = _parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref reader); if (result) { TimeoutControl.CancelTimeout(); } return(result); } finally { _remainingRequestHeadersBytesAllowed -= reader.Consumed - alreadyConsumed; } bool TrimAndTakeMessageHeaders(ref SequenceReader <byte> reader, bool trailers) { var trimmedBuffer = reader.Sequence.Slice(reader.Position, _remainingRequestHeadersBytesAllowed); var trimmedReader = new SequenceReader <byte>(trimmedBuffer); try { if (!_parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref trimmedReader)) { // We read the maximum allowed but didn't complete the headers. KestrelBadHttpRequestException.Throw(RequestRejectionReason.HeadersExceedMaxTotalSize); } TimeoutControl.CancelTimeout(); reader.Advance(trimmedReader.Consumed); return(true); } finally { _remainingRequestHeadersBytesAllowed -= trimmedReader.Consumed; } } }
private void AppendContentLength(ReadOnlySpan <byte> value) { if (_contentLength.HasValue) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.MultipleContentLengths); } if (!Utf8Parser.TryParse(value, out long parsed, out var consumed) || parsed < 0 || consumed != value.Length) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidContentLength, value.GetRequestHeaderString(HeaderNames.ContentLength, EncodingSelector)); } _contentLength = parsed; }
private void AppendContentLengthCustomEncoding(ReadOnlySpan <byte> value, Encoding?customEncoding) { if (_contentLength.HasValue) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.MultipleContentLengths); } // long.MaxValue = 9223372036854775807 (19 chars) Span <char> decodedChars = stackalloc char[20]; var numChars = customEncoding !.GetChars(value, decodedChars); long parsed = -1; if (numChars > 19 || !long.TryParse(decodedChars.Slice(0, numChars), NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed) || parsed < 0) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidContentLength, value.GetRequestHeaderString(HeaderNames.ContentLength, EncodingSelector)); } _contentLength = parsed; }
private void VerifyIsNotReading() { if (!_isReading) { return; } if (_cannotResetInputPipe) { if (_readResult.IsCompleted) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.UnexpectedEndOfRequestContent); } if (_context.RequestTimedOut) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout); } } throw new InvalidOperationException("Reading is already in progress."); }
public override async ValueTask <ReadResult> ReadAsyncInternal(CancellationToken cancellationToken = default) { VerifyIsNotReading(); if (_readCompleted) { _isReading = true; return(new ReadResult(_readResult.Buffer, Interlocked.Exchange(ref _userCanceled, 0) == 1, isCompleted: true)); } // The issue is that TryRead can get a canceled read result // which is unknown to StartTimingReadAsync. if (_context.RequestTimedOut) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout); } await TryStartAsync(); // The while(true) loop is required because the Http1 connection calls CancelPendingRead to unblock // the call to StartTimingReadAsync to check if the request timed out. // However, if the user called CancelPendingRead, we want that to return a canceled ReadResult // We internally track an int for that. while (true) { try { var readAwaitable = _context.Input.ReadAsync(cancellationToken); _isReading = true; _readResult = await StartTimingReadAsync(readAwaitable, cancellationToken); } catch (ConnectionAbortedException ex) { _isReading = false; throw new TaskCanceledException("The request was aborted", ex); } void ResetReadingState() { // Reset the timing read here for the next call to read. StopTimingRead(0); if (!_cannotResetInputPipe) { _isReading = false; _context.Input.AdvanceTo(_readResult.Buffer.Start); } } if (_context.RequestTimedOut) { ResetReadingState(); KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout); } if (_readResult.IsCompleted) { ResetReadingState(); ThrowUnexpectedEndOfRequestContent(); } // Ignore the canceled readResult if it wasn't canceled by the user. // Normally we do not return a canceled ReadResult unless CancelPendingRead was called on the request body PipeReader itself, // but if the last call to AdvanceTo examined data it did not consume, we cannot reset the state of the Input pipe. // https://github.com/dotnet/aspnetcore/issues/19476 if (!_readResult.IsCanceled || Interlocked.Exchange(ref _userCanceled, 0) == 1 || _cannotResetInputPipe) { var returnedReadResultLength = CreateReadResultFromConnectionReadResult(); // Don't count bytes belonging to the next request, since read rate timeouts are done on a per-request basis. StopTimingRead(returnedReadResultLength); if (_readResult.IsCompleted) { TryStop(); } break; } ResetReadingState(); } return(_readResult); }
public override bool TryReadInternal(out ReadResult readResult) { VerifyIsNotReading(); if (_readCompleted) { _isReading = true; readResult = new ReadResult(_readResult.Buffer, Interlocked.Exchange(ref _userCanceled, 0) == 1, isCompleted: true); return(true); } if (_context.RequestTimedOut) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout); } TryStartAsync(); // The while(true) because we don't want to return a canceled ReadResult if the user themselves didn't cancel it. while (true) { if (!_context.Input.TryRead(out _readResult)) { readResult = default; return(false); } if (!_readResult.IsCanceled || Interlocked.Exchange(ref _userCanceled, 0) == 1 || _cannotResetInputPipe) { break; } _context.Input.AdvanceTo(_readResult.Buffer.Start); } if (_readResult.IsCompleted) { if (_cannotResetInputPipe) { _isReading = true; } else { _context.Input.AdvanceTo(_readResult.Buffer.Start); } ThrowUnexpectedEndOfRequestContent(); } var returnedReadResultLength = CreateReadResultFromConnectionReadResult(); // Don't count bytes belonging to the next request, since read rate timeouts are done on a per-request basis. CountBytesRead(returnedReadResultLength); // Only set _isReading if we are returning true. _isReading = true; readResult = _readResult; if (readResult.IsCompleted) { TryStop(); } return(true); }
private async Task PumpAsync() { Debug.Assert(!RequestUpgrade, "Upgraded connections should never use this code path!"); Exception?error = null; try { var awaitable = _context.Input.ReadAsync(); if (!awaitable.IsCompleted) { await TryProduceContinueAsync(); } while (true) { var result = await awaitable; if (_context.RequestTimedOut) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout); } var readableBuffer = result.Buffer; var consumed = readableBuffer.Start; var examined = readableBuffer.Start; try { if (_canceled) { break; } if (!readableBuffer.IsEmpty) { bool done; done = Read(readableBuffer, _requestBodyPipe.Writer, out consumed, out examined); await _requestBodyPipe.Writer.FlushAsync(); if (done) { break; } } // Read() will have already have greedily consumed the entire request body if able. if (result.IsCompleted) { ThrowUnexpectedEndOfRequestContent(); } } finally { _context.Input.AdvanceTo(consumed, examined); } awaitable = _context.Input.ReadAsync(); } } catch (Exception ex) { error = ex; } finally { await _requestBodyPipe.Writer.CompleteAsync(error); } }
public bool ParseHeaders(TRequestHandler handler, ref SequenceReader <byte> reader) { while (!reader.End) { var span = reader.UnreadSpan; while (span.Length > 0) { var ch1 = (byte)0; var ch2 = (byte)0; var readAhead = 0; // Fast path, we're still looking at the same span if (span.Length >= 2) { ch1 = span[0]; ch2 = span[1]; } else if (reader.TryRead(out ch1)) // Possibly split across spans { // Note if we read ahead by 1 or 2 bytes readAhead = (reader.TryRead(out ch2)) ? 2 : 1; } if (ch1 == ByteCR) { // Check for final CRLF. if (ch2 == ByteLF) { // If we got 2 bytes from the span directly so skip ahead 2 so that // the reader's state matches what we expect if (readAhead == 0) { reader.Advance(2); } // Double CRLF found, so end of headers. handler.OnHeadersComplete(endStream: false); return(true); } else if (readAhead == 1) { // Didn't read 2 bytes, reset the reader so we don't consume anything reader.Rewind(1); return(false); } Debug.Assert(readAhead == 0 || readAhead == 2); // Headers don't end in CRLF line. KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidRequestHeadersNoCRLF); } var length = 0; // We only need to look for the end if we didn't read ahead; otherwise there isn't enough in // in the span to contain a header. if (readAhead == 0) { length = span.IndexOfAny(ByteCR, ByteLF); // If not found length with be -1; casting to uint will turn it to uint.MaxValue // which will be larger than any possible span.Length. This also serves to eliminate // the bounds check for the next lookup of span[length] if ((uint)length < (uint)span.Length) { // Early memory read to hide latency var expectedCR = span[length]; // Correctly has a CR, move to next length++; if (expectedCR != ByteCR) { // Sequence needs to be CRLF not LF first. RejectRequestHeader(span[..length]);
public static MessageBody For( HttpVersion httpVersion, HttpRequestHeaders headers, Http1Connection context) { // see also http://tools.ietf.org/html/rfc2616#section-4.4 var keepAlive = httpVersion != HttpVersion.Http10; var upgrade = false; if (headers.HasConnection) { var connectionOptions = HttpHeaders.ParseConnection(headers.HeaderConnection); upgrade = (connectionOptions & ConnectionOptions.Upgrade) != 0; keepAlive = keepAlive || (connectionOptions & ConnectionOptions.KeepAlive) != 0; keepAlive = keepAlive && (connectionOptions & ConnectionOptions.Close) == 0; } // Ignore upgrades if the request has a body. Technically it's possible to support, but we'd have to add a lot // more logic to allow reading/draining the normal body before the connection could be fully upgraded. // See https://tools.ietf.org/html/rfc7230#section-6.7, https://tools.ietf.org/html/rfc7540#section-3.2 if (upgrade && headers.ContentLength.GetValueOrDefault() == 0 && headers.HeaderTransferEncoding.Count == 0) { context.OnTrailersComplete(); // No trailers for these. return(new Http1UpgradeMessageBody(context, keepAlive)); } if (headers.HasTransferEncoding) { var transferEncoding = headers.HeaderTransferEncoding; var transferCoding = HttpHeaders.GetFinalTransferCoding(transferEncoding); // https://tools.ietf.org/html/rfc7230#section-3.3.3 // If a Transfer-Encoding header field // is present in a request and the chunked transfer coding is not // the final encoding, the message body length cannot be determined // reliably; the server MUST respond with the 400 (Bad Request) // status code and then close the connection. if (transferCoding != TransferCoding.Chunked) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.FinalTransferCodingNotChunked, transferEncoding); } // TODO may push more into the wrapper rather than just calling into the message body // NBD for now. return(new Http1ChunkedEncodingMessageBody(context, keepAlive)); } if (headers.ContentLength.HasValue) { var contentLength = headers.ContentLength.Value; if (contentLength == 0) { return(keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose); } return(new Http1ContentLengthMessageBody(context, contentLength, keepAlive)); } // If we got here, request contains no Content-Length or Transfer-Encoding header. // Reject with 411 Length Required. if (context.Method == HttpMethod.Post || context.Method == HttpMethod.Put) { var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10; KestrelBadHttpRequestException.Throw(requestRejectionReason, context.Method); } context.OnTrailersComplete(); // No trailers for these. return(keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose); }
public static MessageBody For( HttpVersion httpVersion, HttpRequestHeaders headers, Http1Connection context) { // see also http://tools.ietf.org/html/rfc2616#section-4.4 var keepAlive = httpVersion != HttpVersion.Http10; var upgrade = false; if (headers.HasConnection) { var connectionOptions = HttpHeaders.ParseConnection(headers.HeaderConnection); upgrade = (connectionOptions & ConnectionOptions.Upgrade) != 0; keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) != 0; } if (upgrade) { if (headers.HeaderTransferEncoding.Count > 0 || (headers.ContentLength.HasValue && headers.ContentLength.Value != 0)) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.UpgradeRequestCannotHavePayload); } context.OnTrailersComplete(); // No trailers for these. return(new Http1UpgradeMessageBody(context)); } if (headers.HasTransferEncoding) { var transferEncoding = headers.HeaderTransferEncoding; var transferCoding = HttpHeaders.GetFinalTransferCoding(transferEncoding); // https://tools.ietf.org/html/rfc7230#section-3.3.3 // If a Transfer-Encoding header field // is present in a request and the chunked transfer coding is not // the final encoding, the message body length cannot be determined // reliably; the server MUST respond with the 400 (Bad Request) // status code and then close the connection. if (transferCoding != TransferCoding.Chunked) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.FinalTransferCodingNotChunked, transferEncoding); } // TODO may push more into the wrapper rather than just calling into the message body // NBD for now. return(new Http1ChunkedEncodingMessageBody(keepAlive, context)); } if (headers.ContentLength.HasValue) { var contentLength = headers.ContentLength.Value; if (contentLength == 0) { return(keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose); } return(new Http1ContentLengthMessageBody(keepAlive, contentLength, context)); } // If we got here, request contains no Content-Length or Transfer-Encoding header. // Reject with 411 Length Required. if (context.Method == HttpMethod.Post || context.Method == HttpMethod.Put) { var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10; KestrelBadHttpRequestException.Throw(requestRejectionReason, context.Method); } context.OnTrailersComplete(); // No trailers for these. return(keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose); }
public override async ValueTask <ReadResult> ReadAsyncInternal(CancellationToken cancellationToken = default) { if (_isReading) { throw new InvalidOperationException("Reading is already in progress."); } if (_readCompleted) { _isReading = true; return(new ReadResult(_readResult.Buffer, Interlocked.Exchange(ref _userCanceled, 0) == 1, _readResult.IsCompleted)); } TryStart(); // The while(true) loop is required because the Http1 connection calls CancelPendingRead to unblock // the call to StartTimingReadAsync to check if the request timed out. // However, if the user called CancelPendingRead, we want that to return a canceled ReadResult // We internally track an int for that. while (true) { // The issue is that TryRead can get a canceled read result // which is unknown to StartTimingReadAsync. if (_context.RequestTimedOut) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout); } try { var readAwaitable = _context.Input.ReadAsync(cancellationToken); _isReading = true; _readResult = await StartTimingReadAsync(readAwaitable, cancellationToken); } catch (ConnectionAbortedException ex) { _isReading = false; throw new TaskCanceledException("The request was aborted", ex); } void ResetReadingState() { _isReading = false; // Reset the timing read here for the next call to read. StopTimingRead(0); _context.Input.AdvanceTo(_readResult.Buffer.Start); } if (_context.RequestTimedOut) { ResetReadingState(); KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout); } if (_readResult.IsCompleted) { ResetReadingState(); ThrowUnexpectedEndOfRequestContent(); } // Ignore the canceled readResult if it wasn't canceled by the user. if (!_readResult.IsCanceled || Interlocked.Exchange(ref _userCanceled, 0) == 1) { var returnedReadResultLength = CreateReadResultFromConnectionReadResult(); // Don't count bytes belonging to the next request, since read rate timeouts are done on a per-request basis. StopTimingRead(returnedReadResultLength); if (_readResult.IsCompleted) { TryStop(); } break; } ResetReadingState(); } return(_readResult); }