public static async Task <HttpResponseMessage> CreateNewAsync(Stream responseStream, HttpMethod requestMethod) { // https://tools.ietf.org/html/rfc7230#section-3 // The normal procedure for parsing an HTTP message is to read the // start - line into a structure, read each header field into a hash table // by field name until the empty line, and then use the parsed data to // determine if a message body is expected.If a message body has been // indicated, then it is read as a stream until an amount of octets // equal to the message body length is read or the connection is closed. // https://tools.ietf.org/html/rfc7230#section-3 // All HTTP/ 1.1 messages consist of a start - line followed by a sequence // of octets in a format similar to the Internet Message Format // [RFC5322]: zero or more header fields(collectively referred to as // the "headers" or the "header section"), an empty line indicating the // end of the header section, and an optional message body. // HTTP - message = start - line // * (header - field CRLF ) // CRLF // [message - body] string startLine = await HttpMessageHelper.ReadStartLineAsync(responseStream).ConfigureAwait(false); var statusLine = StatusLine.Parse(startLine); var response = new HttpResponseMessage(statusLine.StatusCode); string headers = await HttpMessageHelper.ReadHeadersAsync(responseStream).ConfigureAwait(false); var headerSection = await HeaderSection.CreateNewAsync(headers); var headerStruct = headerSection.ToHttpResponseHeaders(); HttpMessageHelper.AssertValidHeaders(headerStruct.ResponseHeaders, headerStruct.ContentHeaders); byte[] contentBytes = await HttpMessageHelper.GetContentBytesAsync(responseStream, headerStruct, requestMethod, statusLine).ConfigureAwait(false); contentBytes = HttpMessageHelper.HandleGzipCompression(headerStruct.ContentHeaders, contentBytes); response.Content = contentBytes is null ? null : new ByteArrayContent(contentBytes); HttpMessageHelper.CopyHeaders(headerStruct.ResponseHeaders, response.Headers); if (response.Content != null) { HttpMessageHelper.CopyHeaders(headerStruct.ContentHeaders, response.Content.Headers); } return(response); }
private static async Task <byte[]> GetDecodedChunkedContentBytesAsync(Stream stream, HttpRequestContentHeaders requestHeaders, HttpResponseContentHeaders responseHeaders, CancellationToken ctsToken = default) { if (responseHeaders is null) { if (requestHeaders is null) { throw new ArgumentException("Response and request headers cannot be both null."); } } else { if (requestHeaders != null) { throw new ArgumentException("Either response or request headers has to be null."); } } // https://tools.ietf.org/html/rfc7230#section-4.1.3 // 4.1.3.Decoding Chunked // A process for decoding the chunked transfer coding can be represented // in pseudo - code as: // length := 0 // read chunk - size, chunk - ext(if any), and CRLF // while (chunk - size > 0) // { // read chunk-data and CRLF // append chunk-data to decoded-body // length:= length + chunk - size // read chunk-size, chunk - ext(if any), and CRLF // } // read trailer field // while (trailer field is not empty) { // if (trailer field is allowed to be sent in a trailer) { // append trailer field to existing header fields // } // read trailer-field // } // Content - Length := length // Remove "chunked" from Transfer-Encoding // Remove Trailer from existing header fields long length = 0; var firstChunkLine = await ReadCRLFLineAsync(stream, Encoding.ASCII, ctsToken : ctsToken); ParseFistChunkLine(firstChunkLine, out long chunkSize, out _); // We will not do anything with the chunk extensions, because: // https://tools.ietf.org/html/rfc7230#section-4.1.1 // A recipient MUST ignore unrecognized chunk extensions. var decodedBody = new List <byte>(); // https://tools.ietf.org/html/rfc7230#section-4.1 // The chunked transfer coding is complete // when a chunk with a chunk-size of zero is received, possibly followed // by a trailer, and finally terminated by an empty line. while (chunkSize > 0) { var chunkData = await ReadBytesTillLengthAsync(stream, chunkSize, ctsToken); string crlfLine = await ReadCRLFLineAsync(stream, Encoding.ASCII, ctsToken); if (crlfLine.Length != 0) { throw new FormatException("Chunk does not end with CRLF."); } decodedBody.AddRange(chunkData); length += chunkSize; firstChunkLine = await ReadCRLFLineAsync(stream, Encoding.ASCII, ctsToken : ctsToken); ParseFistChunkLine(firstChunkLine, out long cs, out _); chunkSize = cs; } // https://tools.ietf.org/html/rfc7230#section-4.1.2 // A trailer allows the sender to include additional fields at the end // of a chunked message in order to supply metadata that might be // dynamically generated while the message body is sent string trailerHeaders = await ReadHeadersAsync(stream, ctsToken); var trailerHeaderSection = await HeaderSection.CreateNewAsync(trailerHeaders); RemoveInvalidTrailers(trailerHeaderSection); if (responseHeaders != null) { var trailerHeaderStruct = trailerHeaderSection.ToHttpResponseHeaders(); AssertValidHeaders(trailerHeaderStruct.ResponseHeaders, trailerHeaderStruct.ContentHeaders); // https://tools.ietf.org/html/rfc7230#section-4.1.2 // When a chunked message containing a non-empty trailer is received, // the recipient MAY process the fields(aside from those forbidden // above) as if they were appended to the message's header section. CopyHeaders(trailerHeaderStruct.ResponseHeaders, responseHeaders.ResponseHeaders); responseHeaders.ResponseHeaders.Remove("Transfer-Encoding"); responseHeaders.ContentHeaders.TryAddWithoutValidation("Content-Length", length.ToString()); responseHeaders.ResponseHeaders.Remove("Trailer"); } if (requestHeaders != null) { var trailerHeaderStruct = trailerHeaderSection.ToHttpRequestHeaders(); AssertValidHeaders(trailerHeaderStruct.RequestHeaders, trailerHeaderStruct.ContentHeaders); // https://tools.ietf.org/html/rfc7230#section-4.1.2 // When a chunked message containing a non-empty trailer is received, // the recipient MAY process the fields(aside from those forbidden // above) as if they were appended to the message's header section. CopyHeaders(trailerHeaderStruct.RequestHeaders, requestHeaders.RequestHeaders); requestHeaders.RequestHeaders.Remove("Transfer-Encoding"); requestHeaders.ContentHeaders.TryAddWithoutValidation("Content-Length", length.ToString()); requestHeaders.RequestHeaders.Remove("Trailer"); } return(decodedBody.ToArray()); }