private void ParseChunkedSuffix(SocketInput input) { var scan = input.ConsumingStart(); var consumed = scan; try { var ch1 = scan.Take(); var ch2 = scan.Take(); if (ch1 == -1 || ch2 == -1) { return; } else if (ch1 == '\r' && ch2 == '\n') { consumed = scan; _mode = Mode.Prefix; } else { _context.RejectRequest("Bad chunk suffix"); } } finally { input.ConsumingComplete(consumed, scan); } }
private void ParseChunkedPrefix(SocketInput input) { var scan = input.ConsumingStart(); var consumed = scan; try { var ch1 = scan.Take(); var ch2 = scan.Take(); if (ch1 == -1 || ch2 == -1) { return; } var chunkSize = CalculateChunkSize(ch1, 0); ch1 = ch2; do { if (ch1 == ';') { consumed = scan; _inputLength = chunkSize; _mode = Mode.Extension; return; } ch2 = scan.Take(); if (ch2 == -1) { return; } if (ch1 == '\r' && ch2 == '\n') { consumed = scan; _inputLength = chunkSize; if (chunkSize > 0) { _mode = Mode.Data; } else { _mode = Mode.Trailer; } return; } chunkSize = CalculateChunkSize(ch1, chunkSize); ch1 = ch2; } while (ch1 != -1); } finally { input.ConsumingComplete(consumed, scan); } }
private void ParseChunkedTrailer(SocketInput input) { var scan = input.ConsumingStart(); var consumed = scan; try { var ch1 = scan.Take(); var ch2 = scan.Take(); if (ch1 == -1 || ch2 == -1) { return; } else if (ch1 == '\r' && ch2 == '\n') { consumed = scan; _mode = Mode.Complete; } else { _mode = Mode.TrailerHeaders; } } finally { input.ConsumingComplete(consumed, scan); } }
public Connection(ListenerContext context, UvStreamHandle socket) : base(context) { _socket = socket; socket.Connection = this; ConnectionControl = this; ConnectionId = GenerateConnectionId(Interlocked.Increment(ref _lastConnectionId)); if (ServerOptions.Limits.MaxRequestBufferSize.HasValue) { _bufferSizeControl = new BufferSizeControl(ServerOptions.Limits.MaxRequestBufferSize.Value, this, Thread); } SocketInput = new SocketInput(Thread.Memory, ThreadPool, _bufferSizeControl); SocketOutput = new SocketOutput(Thread, _socket, this, ConnectionId, Log, ThreadPool); var tcpHandle = _socket as UvTcpHandle; if (tcpHandle != null) { RemoteEndPoint = tcpHandle.GetPeerIPEndPoint(); LocalEndPoint = tcpHandle.GetSockIPEndPoint(); } _frame = FrameFactory(this); _lastTimestamp = Thread.Loop.Now(); }
public ForChunkedEncoding(bool keepAlive, FrameRequestHeaders headers, Frame context) : base(context) { RequestKeepAlive = keepAlive; _input = _context.SocketInput; _requestHeaders = headers; }
private Libuv.uv_buf_t OnAlloc(UvStreamHandle handle, int suggestedSize) { var result = SocketInput.IncomingStart(); return(handle.Libuv.buf_init( result.DataArrayPtr + result.End, result.Data.Offset + result.Data.Count - result.End)); }
private void OnRead(UvStreamHandle handle, int status) { if (status == 0) { // A zero status does not indicate an error or connection end. It indicates // there is no data to be read right now. // See the note at http://docs.libuv.org/en/v1.x/stream.html#c.uv_read_cb. // We need to clean up whatever was allocated by OnAlloc. SocketInput.IncomingDeferred(); return; } var normalRead = status > 0; var normalDone = status == Constants.EOF; var errorDone = !(normalDone || normalRead); var readCount = normalRead ? status : 0; if (normalRead) { Log.ConnectionRead(ConnectionId, readCount); } else { _socket.ReadStop(); if (normalDone) { Log.ConnectionReadFin(ConnectionId); } } IOException error = null; if (errorDone) { Exception uvError; handle.Libuv.Check(status, out uvError); // Log connection resets at a lower (Debug) level. if (status == Constants.ECONNRESET) { Log.ConnectionReset(ConnectionId); } else { Log.ConnectionError(ConnectionId, uvError); } error = new IOException(uvError.Message, uvError); } SocketInput.IncomingComplete(readCount, error); if (errorDone) { Abort(error); } }
public Connection(ListenerContext context, UvStreamHandle socket) : base(context) { _socket = socket; socket.Connection = this; ConnectionControl = this; ConnectionId = GenerateConnectionId(Interlocked.Increment(ref _lastConnectionId)); if (ServerOptions.MaxRequestBufferSize.HasValue) { _bufferSizeControl = new BufferSizeControl(ServerOptions.MaxRequestBufferSize.Value, this, Thread); } _rawSocketInput = new SocketInput(Memory, ThreadPool, _bufferSizeControl); _rawSocketOutput = new SocketOutput(Thread, _socket, Memory, this, ConnectionId, Log, ThreadPool, WriteReqPool); }
private void ParseExtension(SocketInput input) { var scan = input.ConsumingStart(); var consumed = scan; try { // Chunk-extensions not currently parsed // Just drain the data do { if (scan.Seek(ref _vectorCRs) == -1) { // End marker not found yet consumed = scan; return; } ; var ch1 = scan.Take(); var ch2 = scan.Take(); if (ch2 == '\n') { consumed = scan; if (_inputLength > 0) { _mode = Mode.Data; } else { _mode = Mode.Trailer; } } else if (ch2 == -1) { return; } } while (_mode == Mode.Extension); } finally { input.ConsumingComplete(consumed, scan); } }
private static async Task <ArraySegment <byte> > PeekAsyncAwaited(this SocketInput input) { while (true) { await input; var fin = input.CheckFinOrThrow(); var begin = input.ConsumingStart(); var segment = begin.PeekArraySegment(); input.ConsumingComplete(begin, begin); if (segment.Count != 0 || fin) { return(segment); } } }
public static ValueTask <ArraySegment <byte> > PeekAsync(this SocketInput input) { while (input.IsCompleted) { var fin = input.CheckFinOrThrow(); var begin = input.ConsumingStart(); var segment = begin.PeekArraySegment(); input.ConsumingComplete(begin, begin); if (segment.Count != 0 || fin) { return(new ValueTask <ArraySegment <byte> >(segment)); } } return(new ValueTask <ArraySegment <byte> >(input.PeekAsyncAwaited())); }
private static async Task <int> ReadAsyncAwaited(this SocketInput input, byte[] buffer, int offset, int count) { while (true) { await input; var fin = input.CheckFinOrThrow(); var begin = input.ConsumingStart(); int actual; var end = begin.CopyTo(buffer, offset, count, out actual); input.ConsumingComplete(end, end); if (actual != 0 || fin) { return(actual); } } }
public static ValueTask <int> ReadAsync(this SocketInput input, byte[] buffer, int offset, int count) { while (input.IsCompleted) { var fin = input.CheckFinOrThrow(); var begin = input.ConsumingStart(); int actual; var end = begin.CopyTo(buffer, offset, count, out actual); input.ConsumingComplete(end, end); if (actual != 0 || fin) { return(new ValueTask <int>(actual)); } } return(new ValueTask <int>(input.ReadAsyncAwaited(buffer, offset, count))); }
void IConnectionControl.Resume() { if (_socket.IsClosed) { return; } Log.ConnectionResume(ConnectionId); try { _socket.ReadStart(_allocCallback, _readCallback, this); } catch (UvException) { // ReadStart() can throw a UvException in some cases (e.g. socket is no longer connected). // This should be treated the same as OnRead() seeing a "normalDone" condition. Log.ConnectionReadFin(ConnectionId); SocketInput.IncomingComplete(0, null); } }
// Called on Libuv thread public virtual void OnSocketClosed() { _frame.FrameStartedTask.ContinueWith((task, state) => { var connection = (Connection)state; if (_filteredStreamAdapter != null) { Task.WhenAll(_readInputTask, _frame.StopAsync()).ContinueWith((task2, state2) => { var connection2 = (Connection)state2; connection2._filterContext.Connection.Dispose(); connection2._filteredStreamAdapter.Dispose(); }, connection); } }, this); SocketInput.Dispose(); _socketClosedTcs.TrySetResult(null); }
private int ReadChunkedData(SocketInput input, byte[] buffer, int offset, int count) { var scan = input.ConsumingStart(); int actual; try { var limit = buffer == null ? _inputLength : Math.Min(count, _inputLength); scan = scan.CopyTo(buffer, offset, limit, out actual); _inputLength -= actual; } finally { input.ConsumingComplete(scan, scan); } if (_inputLength == 0) { _mode = Mode.Suffix; } return(actual); }
public Task StopAsync() { lock (_stateLock) { switch (_connectionState) { case ConnectionState.SocketClosed: return(TaskUtilities.CompletedTask); case ConnectionState.CreatingFrame: _connectionState = ConnectionState.ToDisconnect; break; case ConnectionState.Open: _frame.Stop(); SocketInput.CompleteAwaiting(); break; } _socketClosedTcs = new TaskCompletionSource <object>(); return(_socketClosedTcs.Task); } }
/// <summary> /// Primary loop which consumes socket input, parses it for protocol framing, and invokes the /// application delegate for as long as the socket is intended to remain open. /// The resulting Task from this loop is preserved in a field which is used when the server needs /// to drain and close all currently active connections. /// </summary> public override async Task RequestProcessingAsync() { var requestLineStatus = RequestLineStatus.Empty; try { while (!_requestProcessingStopping) { ConnectionControl.SetTimeout(_keepAliveMilliseconds, TimeoutAction.CloseConnection); while (!_requestProcessingStopping) { requestLineStatus = TakeStartLine(SocketInput); if (requestLineStatus == RequestLineStatus.Done) { break; } if (SocketInput.CheckFinOrThrow()) { // We need to attempt to consume start lines and headers even after // SocketInput.RemoteIntakeFin is set to true to ensure we don't close a // connection without giving the application a chance to respond to a request // sent immediately before the a FIN from the client. requestLineStatus = TakeStartLine(SocketInput); if (requestLineStatus == RequestLineStatus.Empty) { return; } if (requestLineStatus != RequestLineStatus.Done) { RejectRequest(RequestRejectionReason.InvalidRequestLine, requestLineStatus.ToString()); } break; } await SocketInput; } InitializeHeaders(); while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, FrameRequestHeaders)) { if (SocketInput.CheckFinOrThrow()) { // We need to attempt to consume start lines and headers even after // SocketInput.RemoteIntakeFin is set to true to ensure we don't close a // connection without giving the application a chance to respond to a request // sent immediately before the a FIN from the client. if (!TakeMessageHeaders(SocketInput, FrameRequestHeaders)) { RejectRequest(RequestRejectionReason.MalformedRequestInvalidHeaders); } break; } await SocketInput; } if (!_requestProcessingStopping) { var messageBody = MessageBody.For(_httpVersion, FrameRequestHeaders, this); _keepAlive = messageBody.RequestKeepAlive; _upgrade = messageBody.RequestUpgrade; InitializeStreams(messageBody); var context = _application.CreateContext(this); try { try { await _application.ProcessRequestAsync(context).ConfigureAwait(false); if (Volatile.Read(ref _requestAborted) == 0) { VerifyResponseContentLength(); } } catch (Exception ex) { ReportApplicationError(ex); if (ex is BadHttpRequestException) { throw; } } finally { // Trigger OnStarting if it hasn't been called yet and the app hasn't // already failed. If an OnStarting callback throws we can go through // our normal error handling in ProduceEnd. // https://github.com/aspnet/KestrelHttpServer/issues/43 if (!HasResponseStarted && _applicationException == null && _onStarting != null) { await FireOnStarting(); } PauseStreams(); if (_onCompleted != null) { await FireOnCompleted(); } } // If _requestAbort is set, the connection has already been closed. if (Volatile.Read(ref _requestAborted) == 0) { ResumeStreams(); if (HasResponseStarted) { // If the response has already started, call ProduceEnd() before // consuming the rest of the request body to prevent // delaying clients waiting for the chunk terminator: // // https://github.com/dotnet/corefx/issues/17330#issuecomment-288248663 // // ProduceEnd() must be called before _application.DisposeContext(), to ensure // HttpContext.Response.StatusCode is correctly set when // IHttpContextFactory.Dispose(HttpContext) is called. await ProduceEnd(); } if (_keepAlive) { // Finish reading the request body in case the app did not. await messageBody.Consume(); } if (!HasResponseStarted) { await ProduceEnd(); } } else if (!HasResponseStarted) { // If the request was aborted and no response was sent, there's no // meaningful status code to log. StatusCode = 0; } } catch (BadHttpRequestException ex) { // Handle BadHttpRequestException thrown during app execution or remaining message body consumption. // This has to be caught here so StatusCode is set properly before disposing the HttpContext // (DisposeContext logs StatusCode). SetBadRequestState(ex); } finally { _application.DisposeContext(context, _applicationException); // StopStreams should be called before the end of the "if (!_requestProcessingStopping)" block // to ensure InitializeStreams has been called. StopStreams(); } } if (!_keepAlive) { // End the connection for non keep alive as data incoming may have been thrown off return; } // Don't reset frame state if we're exiting the loop. This avoids losing request rejection // information (for 4xx response), and prevents ObjectDisposedException on HTTPS (ODEs // will be thrown if PrepareRequest is not null and references objects disposed on connection // close - see https://github.com/aspnet/KestrelHttpServer/issues/1103#issuecomment-250237677). if (!_requestProcessingStopping) { Reset(); } } } catch (BadHttpRequestException ex) { // Handle BadHttpRequestException thrown during request line or header parsing. // SetBadRequestState logs the error. SetBadRequestState(ex); } catch (IOException ex) when(ex.InnerException is UvException) { // Don't log ECONNRESET errors made between requests. Browsers like IE will reset connections regularly. if (requestLineStatus != RequestLineStatus.Empty || ((UvException)ex.InnerException).StatusCode != Constants.ECONNRESET) { Log.RequestProcessingError(ConnectionId, ex); } } catch (Exception ex) { Log.LogWarning(0, ex, "Connection processing ended abnormally"); } finally { try { // If _requestAborted is set, the connection has already been closed. if (Volatile.Read(ref _requestAborted) == 0) { await TryProduceInvalidRequestResponse(); ConnectionControl.End(ProduceEndType.SocketShutdown); } } catch (Exception ex) { Log.LogWarning(0, ex, "Connection shutdown abnormally"); } } }
public bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders) { var scan = input.ConsumingStart(); var consumed = scan; try { while (!scan.IsEnd) { var ch = scan.Peek(); if (ch == -1) { return(false); } else if (ch == '\r') { // Check for final CRLF. scan.Take(); ch = scan.Take(); if (ch == -1) { return(false); } else if (ch == '\n') { consumed = scan; return(true); } // Headers don't end in CRLF line. RejectRequest("Headers corrupted, invalid header sequence."); } else if (ch == ' ' || ch == '\t') { RejectRequest("Header line must not start with whitespace."); } var beginName = scan; if (scan.Seek(ref _vectorColons, ref _vectorCRs) == -1) { return(false); } var endName = scan; ch = scan.Take(); if (ch != ':') { RejectRequest("No ':' character found in header line."); } var validateName = beginName; if (validateName.Seek(ref _vectorSpaces, ref _vectorTabs, ref _vectorColons) != ':') { RejectRequest("Whitespace is not allowed in header name."); } var beginValue = scan; ch = scan.Peek(); if (ch == -1) { return(false); } // Skip header value leading whitespace. while (ch == ' ' || ch == '\t') { scan.Take(); beginValue = scan; ch = scan.Peek(); if (ch == -1) { return(false); } } scan = beginValue; if (scan.Seek(ref _vectorCRs) == -1) { // no "\r" in sight, burn used bytes and go back to await more data return(false); } scan.Take(); // we know this is '\r' ch = scan.Take(); // expecting '\n' if (ch == -1) { return(false); } else if (ch != '\n') { RejectRequest("Header line must end in CRLF; only CR found."); } var next = scan.Peek(); if (next == -1) { return(false); } else if (next == ' ' || next == '\t') { // From https://tools.ietf.org/html/rfc7230#section-3.2.4: // // Historically, HTTP header field values could be extended over // multiple lines by preceding each extra line with at least one space // or horizontal tab (obs-fold). This specification deprecates such // line folding except within the message/http media type // (Section 8.3.1). A sender MUST NOT generate a message that includes // line folding (i.e., that has any field-value that contains a match to // the obs-fold rule) unless the message is intended for packaging // within the message/http media type. // // A server that receives an obs-fold in a request message that is not // within a message/http container MUST either reject the message by // sending a 400 (Bad Request), preferably with a representation // explaining that obsolete line folding is unacceptable, or replace // each received obs-fold with one or more SP octets prior to // interpreting the field value or forwarding the message downstream. RejectRequest("Header value line folding not supported."); } // Trim trailing whitespace from header value by repeatedly advancing to next // whitespace or CR. // // - If CR is found, this is the end of the header value. // - If whitespace is found, this is the _tentative_ end of the header value. // If non-whitespace is found after it and it's not CR, seek again to the next // whitespace or CR for a new (possibly tentative) end of value. var ws = beginValue; var endValue = scan; do { ws.Seek(ref _vectorSpaces, ref _vectorTabs, ref _vectorCRs); endValue = ws; ch = ws.Take(); while (ch == ' ' || ch == '\t') { ch = ws.Take(); } } while (ch != '\r'); var name = beginName.GetArraySegment(endName); var value = beginValue.GetAsciiString(endValue); consumed = scan; requestHeaders.Append(name.Array, name.Offset, name.Count, value); } return(false); } finally { input.ConsumingComplete(consumed, scan); } }
protected RequestLineStatus TakeStartLine(SocketInput input) { var scan = input.ConsumingStart(); var consumed = scan; try { // We may hit this when the client has stopped sending data but // the connection hasn't closed yet, and therefore Frame.Stop() // hasn't been called yet. if (scan.Peek() == -1) { return(RequestLineStatus.Empty); } _requestProcessingStatus = RequestProcessingStatus.RequestStarted; string method; var begin = scan; if (!begin.GetKnownMethod(out method)) { if (scan.Seek(ref _vectorSpaces) == -1) { return(RequestLineStatus.MethodIncomplete); } method = begin.GetAsciiString(scan); if (method == null) { RejectRequest("Missing method."); } // Note: We're not in the fast path any more (GetKnownMethod should have handled any HTTP Method we're aware of) // So we can be a tiny bit slower and more careful here. for (int i = 0; i < method.Length; i++) { if (!IsValidTokenChar(method[i])) { RejectRequest("Invalid method."); } } } else { scan.Skip(method.Length); } scan.Take(); begin = scan; var needDecode = false; var chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks, ref _vectorPercentages); if (chFound == -1) { return(RequestLineStatus.TargetIncomplete); } else if (chFound == '%') { needDecode = true; chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks); if (chFound == -1) { return(RequestLineStatus.TargetIncomplete); } } var pathBegin = begin; var pathEnd = scan; var queryString = ""; if (chFound == '?') { begin = scan; if (scan.Seek(ref _vectorSpaces) == -1) { return(RequestLineStatus.TargetIncomplete); } queryString = begin.GetAsciiString(scan); } var queryEnd = scan; if (pathBegin.Peek() == ' ') { RejectRequest("Missing request target."); } scan.Take(); begin = scan; if (scan.Seek(ref _vectorCRs) == -1) { return(RequestLineStatus.VersionIncomplete); } string httpVersion; if (!begin.GetKnownVersion(out httpVersion)) { // A slower fallback is necessary since the iterator's PeekLong() method // used in GetKnownVersion() only examines two memory blocks at most. // Although unlikely, it is possible that the 8 bytes forming the version // could be spread out on more than two blocks, if the connection // happens to be unusually slow. httpVersion = begin.GetAsciiString(scan); if (httpVersion == null) { RejectRequest("Missing HTTP version."); } else if (httpVersion != "HTTP/1.0" && httpVersion != "HTTP/1.1") { RejectRequest("Unrecognized HTTP version."); } } scan.Take(); var next = scan.Take(); if (next == -1) { return(RequestLineStatus.Incomplete); } else if (next != '\n') { RejectRequest("Missing LF in request line."); } // URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11 // Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8; // then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs" string requestUrlPath; string rawTarget; if (needDecode) { // Read raw target before mutating memory. rawTarget = pathBegin.GetAsciiString(queryEnd); // URI was encoded, unescape and then parse as utf8 pathEnd = UrlPathDecoder.Unescape(pathBegin, pathEnd); requestUrlPath = pathBegin.GetUtf8String(pathEnd); } else { // URI wasn't encoded, parse as ASCII requestUrlPath = pathBegin.GetAsciiString(pathEnd); if (queryString.Length == 0) { // No need to allocate an extra string if the path didn't need // decoding and there's no query string following it. rawTarget = requestUrlPath; } else { rawTarget = pathBegin.GetAsciiString(queryEnd); } } var normalizedTarget = PathNormalizer.RemoveDotSegments(requestUrlPath); consumed = scan; Method = method; QueryString = queryString; RawTarget = rawTarget; HttpVersion = httpVersion; bool caseMatches; if (RequestUrlStartsWithPathBase(normalizedTarget, out caseMatches)) { PathBase = caseMatches ? _pathBase : normalizedTarget.Substring(0, _pathBase.Length); Path = normalizedTarget.Substring(_pathBase.Length); } else if (rawTarget[0] == '/') // check rawTarget since normalizedTarget can be "" or "/" after dot segment removal { Path = normalizedTarget; } else { Path = string.Empty; PathBase = string.Empty; QueryString = string.Empty; } return(RequestLineStatus.Done); } finally { input.ConsumingComplete(consumed, scan); } }
private async Task <int> ReadStateMachineAsync(SocketInput input, ArraySegment <byte> buffer, CancellationToken cancellationToken) { while (_mode < Mode.Trailer) { while (_mode == Mode.Prefix) { var fin = input.RemoteIntakeFin; ParseChunkedPrefix(input); if (_mode != Mode.Prefix) { break; } else if (fin) { ThrowChunkedRequestIncomplete(); } await input; } while (_mode == Mode.Extension) { var fin = input.RemoteIntakeFin; ParseExtension(input); if (_mode != Mode.Extension) { break; } else if (fin) { ThrowChunkedRequestIncomplete(); } await input; } while (_mode == Mode.Data) { var fin = input.RemoteIntakeFin; int actual = ReadChunkedData(input, buffer.Array, buffer.Offset, buffer.Count); if (actual != 0) { return(actual); } else if (_mode != Mode.Data) { break; } else if (fin) { ThrowChunkedRequestIncomplete(); } await input; } while (_mode == Mode.Suffix) { var fin = input.RemoteIntakeFin; ParseChunkedSuffix(input); if (_mode != Mode.Suffix) { break; } else if (fin) { ThrowChunkedRequestIncomplete(); } await input; } } // Chunks finished, parse trailers while (_mode == Mode.Trailer) { var fin = input.RemoteIntakeFin; ParseChunkedTrailer(input); if (_mode != Mode.Trailer) { break; } else if (fin) { ThrowChunkedRequestIncomplete(); } await input; } if (_mode == Mode.TrailerHeaders) { while (!_context.TakeMessageHeaders(input, _requestHeaders)) { if (input.RemoteIntakeFin) { if (_context.TakeMessageHeaders(input, _requestHeaders)) { break; } else { ThrowChunkedRequestIncomplete(); } } await input; } _mode = Mode.Complete; } return(0); }