Beispiel #1
0
        public Task WriteHeadersAsync(
            IEnumerable <HeaderField> headers, bool endOfStream)
        {
            HeaderValidationResult hvr;

            // TODO: For push promises other validates might need to be used
            if (connection.IsServer)
            {
                hvr = HeaderValidator.ValidateResponseHeaders(headers);
            }
            else
            {
                hvr = HeaderValidator.ValidateRequestHeaders(headers);
            }
            if (hvr != HeaderValidationResult.Ok)
            {
                throw new Exception(hvr.ToString());
            }

            return(WriteValidatedHeadersAsync(headers, endOfStream));
        }
Beispiel #2
0
        /// <summary>
        /// Processes the reception of incoming headers
        /// </summary>
        public Http2Error?ProcessHeaders(
            CompleteHeadersFrameData headers)
        {
            var wakeupDataWaiter    = false;
            var wakeupHeaderWaiter  = false;
            var wakeupTrailerWaiter = false;
            var removeStream        = false;

            lock (stateMutex)
            {
                // Header frames are not valid in all states
                switch (state)
                {
                case StreamState.ReservedLocal:
                case StreamState.ReservedRemote:
                    // Push promises are currently not implemented
                    // So this needs to be reviewed later on
                    // Currently we should never encounter this state
                    return(new Http2Error
                    {
                        StreamId = Id,
                        Code = ErrorCode.InternalError,
                        Message = "Received header frame in uncovered push promise state",
                    });

                case StreamState.Idle:
                case StreamState.Open:
                case StreamState.HalfClosedLocal:
                    // Open can mean we have already received headers
                    // (in case we are a server) or not (in case we are
                    // a client and only have sent headers)
                    // If headers were already received before there must be
                    // a data frame in between and these are trailers.
                    // An exception is if we are client, where we can
                    // receive informational headers and normal headers.
                    // This requires no data in between. These header must
                    // contain a 1xy status code.
                    // Trailers must have the EndOfStream flag set and must
                    // always follow after a data frame.
                    if (headersReceived != HeaderReceptionState.ReceivedAllHeaders)
                    {
                        // We are receiving headers
                        HeaderValidationResult hvr;
                        if (connection.IsServer)
                        {
                            hvr = HeaderValidator.ValidateRequestHeaders(headers.Headers);
                        }
                        else
                        {
                            hvr = HeaderValidator.ValidateResponseHeaders(headers.Headers);
                        }
                        if (hvr != HeaderValidationResult.Ok)
                        {
                            return(new Http2Error
                            {
                                StreamId = Id,
                                Code = ErrorCode.ProtocolError,
                                Message = "Received invalid headers",
                            });
                        }

                        if (!connection.config.IsServer &&
                            headers.Headers.IsInformationalHeaders())
                        {
                            // Clients support the reception of informational headers.
                            // If this is only an informational header we might
                            // receive additional headers later on.
                            headersReceived =
                                HeaderReceptionState.ReceivedInformationalHeaders;
                        }
                        else
                        {
                            // Servers don't support informational headers at all.
                            // And if we are client and directly receive response
                            // headers it's also fine.
                            headersReceived =
                                HeaderReceptionState.ReceivedAllHeaders;
                        }
                        wakeupHeaderWaiter = true;
                        // TODO: Uncompress cookie headers here?
                        declaredInContentLength = headers.Headers.GetContentLength();
                        inHeaders = headers.Headers;
                    }
                    else if (!dataReceived)
                    {
                        // We already have received headers, so this should
                        // be trailers. However there was no DATA frame in
                        // between, so this is simply invalid.
                        return(new Http2Error
                        {
                            StreamId = Id,
                            Code = ErrorCode.ProtocolError,
                            Message = "Received trailers without headers",
                        });
                    }
                    else
                    {
                        // These are trailers
                        // trailers must have end of stream set. It is not
                        // valid to receive multiple trailers
                        if (!headers.EndOfStream)
                        {
                            return(new Http2Error
                            {
                                StreamId = Id,
                                Code = ErrorCode.ProtocolError,
                                Message = "Received trailers without EndOfStream flag",
                            });
                        }
                        var hvr = HeaderValidator.ValidateTrailingHeaders(headers.Headers);
                        if (hvr != HeaderValidationResult.Ok)
                        {
                            return(new Http2Error
                            {
                                StreamId = Id,
                                Code = ErrorCode.ProtocolError,
                                Message = "Received invalid trailers",
                            });
                        }

                        // If content-length was set we must also validate
                        // it against the received dataamount here
                        if (declaredInContentLength >= 0 &&
                            declaredInContentLength != totalInData)
                        {
                            return(new Http2Error
                            {
                                StreamId = Id,
                                Code = ErrorCode.ProtocolError,
                                Message =
                                    "Length of DATA frames does not match content-length",
                            });
                        }

                        wakeupTrailerWaiter = true;
                        inTrailers          = headers.Headers;
                    }

                    // Handle state changes that are caused by HEADERS frame
                    if (state == StreamState.Idle)
                    {
                        state = StreamState.Open;
                    }
                    if (headers.EndOfStream)
                    {
                        if (state == StreamState.HalfClosedLocal)
                        {
                            state        = StreamState.Closed;
                            removeStream = true;
                        }
                        else     // Must be Open, since Idle moves to Open
                        {
                            state = StreamState.HalfClosedRemote;
                        }
                        wakeupTrailerWaiter = true;
                        wakeupDataWaiter    = true;
                    }
                    break;

                case StreamState.HalfClosedRemote:
                case StreamState.Closed:
                    // Received a header frame for a stream that was
                    // already closed from remote side.
                    // That's not valid
                    return(new Http2Error
                    {
                        Code = ErrorCode.StreamClosed,
                        StreamId = Id,
                        Message = "Received headers for closed stream",
                    });

                case StreamState.Reset:
                    // The stream was already reset
                    // What we really should do here depends on the previous state,
                    // which is not stored for efficiency. If we reset the
                    // stream late headers are ok. If the remote resetted it
                    // this is a protocol error for the stream.
                    // As it does not really matter just ignore the frame.
                    break;

                default:
                    throw new Exception("Unhandled stream state");
                }
            }

            // Wakeup any blocked calls that are waiting on headers or end of stream
            if (wakeupHeaderWaiter)
            {
                readHeadersPossible.Set();
            }
            if (wakeupDataWaiter)
            {
                readDataPossible.Set();
            }
            if (wakeupTrailerWaiter)
            {
                readTrailersPossible.Set();
            }

            if (removeStream)
            {
                connection.UnregisterStream(this);
            }

            return(null);
        }
Beispiel #3
0
        public async Task WriteTrailersAsync(IEnumerable <HeaderField> headers)
        {
            HeaderValidationResult hvr = HeaderValidator.ValidateTrailingHeaders(headers);

            // TODO: For push promises other validates might need to be used
            if (hvr != HeaderValidationResult.Ok)
            {
                throw new Exception(hvr.ToString());
            }

            var removeStream = false;

            await writeMutex.WaitAsync();

            try
            {
                // Check what already has been sent
                lock (stateMutex)
                {
                    if (!dataSent)
                    {
                        throw new Exception("Attempted to write trailers without data");
                    }

                    switch (state)
                    {
                    case StreamState.Open:
                        state = StreamState.HalfClosedLocal;
                        break;

                    case StreamState.HalfClosedRemote:
                        state        = StreamState.HalfClosedRemote;
                        state        = StreamState.Closed;
                        removeStream = true;
                        break;

                    case StreamState.Idle:
                    case StreamState.ReservedRemote:
                    case StreamState.HalfClosedLocal:
                    case StreamState.Closed:
                        throw new Exception("Invalid state for sending trailers");

                    case StreamState.Reset:
                        throw new StreamResetException();

                    case StreamState.ReservedLocal:
                        // We can't be in here if we already have data sent
                        throw new Exception("Unexpected state: ReservedLocal after data sent");
                    }
                }

                await SendHeaders(headers, true); // TODO: Use result
            }
            finally
            {
                writeMutex.Release();
                if (removeStream)
                {
                    connection.UnregisterStream(this);
                }
            }
        }
        /// <summary>
        /// Builds a ServerUpgradeRequest from the stored configuration.
        /// All other relevant configuration setter methods need to be called
        /// before.
        /// Only if the ServerUpgradeRequest.IsValid is true an upgrade
        /// to HTTP/2 may be performed.
        /// </summary>
        public ServerUpgradeRequest Build()
        {
            bool valid = true;

            if (settings == null)
            {
                valid = false;
            }

            var headers = this.headers;

            if (headers == null)
            {
                valid = false;
            }
            else
            {
                // Validate headers
                var hvr = HeaderValidator.ValidateRequestHeaders(headers);
                if (hvr != HeaderValidationResult.Ok)
                {
                    headers = null;
                    valid   = false;
                }
            }

            long declaredContentLength = -1;

            if (headers != null)
            {
                declaredContentLength = headers.GetContentLength();
                // TODO: Handle invalid content lengths, which would yield
                // results like -2?
            }

            byte[] payload = null;
            if (this.payload != null && this.payload.Count > 0)
            {
                // Compare the declared content length against the actual
                // content length
                if (declaredContentLength != this.payload.Count)
                {
                    valid = false;
                }
                else
                {
                    payload = new byte[this.payload.Count];
                    Array.Copy(
                        this.payload.Array, this.payload.Offset,
                        payload, 0,
                        this.payload.Count);
                }
            }
            else if (declaredContentLength > 0)
            {
                // No payload but Content-Length header was found
                valid = false;
            }

            return(new ServerUpgradeRequest(
                       settings: settings ?? Settings.Default,
                       headers: headers,
                       payload: payload,
                       valid: valid));
        }