public static async Task <HttpContent> GetContentAsync(Stream stream, HttpRequestContentHeaders headerStruct, CancellationToken ctsToken = default) { if (headerStruct.RequestHeaders != null && headerStruct.RequestHeaders.Contains("Transfer-Encoding")) { // https://tools.ietf.org/html/rfc7230#section-4 // All transfer-coding names are case-insensitive if ("chunked".Equals(headerStruct.RequestHeaders.TransferEncoding.Last().Value, StringComparison.OrdinalIgnoreCase)) { return(await GetDecodedChunkedContentAsync(stream, headerStruct, ctsToken)); } // https://tools.ietf.org/html/rfc7230#section-3.3.3 // If a Transfer - Encoding header field is present in a response and // the chunked transfer coding is not the final encoding, the // message body length is determined by reading the connection until // it is closed by the server. If a Transfer - Encoding header field // is present in a request and the chunked transfer coding is not // the final encoding, the message body length cannot be determined // reliably; the server MUST respond with the 400(Bad Request) // status code and then close the connection. else { return(await GetContentTillEndAsync(stream, ctsToken)); } } // https://tools.ietf.org/html/rfc7230#section-3.3.3 // 5.If a valid Content - Length header field is present without // Transfer - Encoding, its decimal value defines the expected message // body length in octets.If the sender closes the connection or // the recipient times out before the indicated number of octets are // received, the recipient MUST consider the message to be // incomplete and close the connection. else if (headerStruct.ContentHeaders.Contains("Content-Length")) { long?contentLength = headerStruct.ContentHeaders?.ContentLength; return(await GetContentTillLengthAsync(stream, contentLength, ctsToken)); } // https://tools.ietf.org/html/rfc7230#section-3.3.3 // 6.If this is a request message and none of the above are true, then // the message body length is zero (no message body is present). // 7. Otherwise, this is a response message without a declared message // body length, so the message body length is determined by the // number of octets received prior to the server closing the // connection. return(GetDummyOrNullContent(headerStruct.ContentHeaders)); }
private static async Task <HttpContent> GetDecodedChunkedContentAsync(Stream stream, HttpRequestContentHeaders requestHeaders, HttpResponseContentHeaders responseHeaders, CancellationToken ctsToken = default) { if (responseHeaders == null && requestHeaders == null) { throw new ArgumentException("Response and request headers cannot be both null."); } if (responseHeaders != null && 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 IEnumerable <string> chunkExtensions); // 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); if (await ReadCRLFLineAsync(stream, Encoding.ASCII, ctsToken) != "") { throw new FormatException("Chunk does not end with CRLF."); } foreach (var b in chunkData) { decodedBody.Add(b); } length += chunkSize; firstChunkLine = await ReadCRLFLineAsync(stream, Encoding.ASCII, ctsToken : ctsToken); ParseFistChunkLine(firstChunkLine, out long cs, out IEnumerable <string> ces); chunkSize = cs; chunkExtensions = ces; } // 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 = HeaderSection.CreateNew(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(new ByteArrayContent(decodedBody.ToArray())); }
private static async Task <HttpContent> GetDecodedChunkedContentAsync(Stream stream, HttpRequestContentHeaders headerStruct, CancellationToken ctsToken = default) { return(await GetDecodedChunkedContentAsync(stream, headerStruct, null, ctsToken)); }