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)); }
/// <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); }
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)); }