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);
                }
            }
예제 #4
0
        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();
        }
예제 #5
0
 public ForChunkedEncoding(bool keepAlive, FrameRequestHeaders headers, Frame context)
     : base(context)
 {
     RequestKeepAlive = keepAlive;
     _input           = _context.SocketInput;
     _requestHeaders  = headers;
 }
예제 #6
0
        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));
        }
예제 #7
0
        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)));
        }
예제 #14
0
        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);
            }
        }
예제 #15
0
        // 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);
            }
        }
예제 #18
0
        /// <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");
                }
            }
        }
예제 #19
0
        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);
            }
        }
예제 #20
0
        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);
            }