Example #1
0
 private static async Task <byte[]> GetDecodedChunkedContentBytesAsync(Stream stream, HttpResponseContentHeaders headerStruct, CancellationToken ctsToken = default)
 {
     return(await GetDecodedChunkedContentBytesAsync(stream, null, headerStruct, ctsToken));
 }
        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);

                if (await ReadCRLFLineAsync(stream, Encoding.ASCII, ctsToken) != "")
                {
                    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());
        }
Example #3
0
        public static async Task <byte[]> GetContentBytesAsync(Stream stream, HttpResponseContentHeaders headerStruct, HttpMethod requestMethod, StatusLine statusLine, CancellationToken ctsToken = default)
        {
            // https://tools.ietf.org/html/rfc7230#section-3.3.3
            // The length of a message body is determined by one of the following
            // (in order of precedence):
            // 1.Any response to a HEAD request and any response with a 1xx
            // (Informational), 204(No Content), or 304(Not Modified) status
            // code is always terminated by the first empty line after the
            // header fields, regardless of the header fields present in the
            // message, and thus cannot contain a message body.
            if (requestMethod == HttpMethod.Head ||
                HttpStatusCodeHelper.IsInformational(statusLine.StatusCode) ||
                statusLine.StatusCode == HttpStatusCode.NoContent ||
                statusLine.StatusCode == HttpStatusCode.NotModified)
            {
                return(GetDummyOrNullContentBytes(headerStruct.ContentHeaders));
            }
            // https://tools.ietf.org/html/rfc7230#section-3.3.3
            // 2.Any 2xx(Successful) response to a CONNECT request implies that
            // the connection will become a tunnel immediately after the empty
            // line that concludes the header fields.A client MUST ignore any
            // Content - Length or Transfer-Encoding header fields received in
            // such a message.
            if (requestMethod == new HttpMethod("CONNECT"))
            {
                if (HttpStatusCodeHelper.IsSuccessful(statusLine.StatusCode))
                {
                    return(null);
                }
            }

            // https://tools.ietf.org/html/rfc7230#section-3.3.3
            // 3.If a Transfer-Encoding header field is present and the chunked
            // transfer coding(Section 4.1) is the final encoding, the message
            // body length is determined by reading and decoding the chunked
            // data until the transfer coding indicates the data is complete.
            if (headerStruct?.ResponseHeaders != null && headerStruct.ResponseHeaders.Contains("Transfer-Encoding"))
            {
                // https://tools.ietf.org/html/rfc7230#section-4
                // All transfer-coding names are case-insensitive
                if ("chunked".Equals(headerStruct.ResponseHeaders.TransferEncoding.Last().Value, StringComparison.OrdinalIgnoreCase))
                {
                    return(await GetDecodedChunkedContentBytesAsync(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.
                return(await GetBytesTillEndAsync(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.
            if (headerStruct.ContentHeaders.Contains("Content-Length"))
            {
                long?contentLength = headerStruct.ContentHeaders?.ContentLength;

                return(await ReadBytesTillLengthAsync(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(await GetBytesTillEndAsync(stream, ctsToken));
        }
Example #4
0
 private static async Task <HttpContent> GetDecodedChunkedContentAsync(Stream stream, HttpResponseContentHeaders headerStruct, CancellationToken ctsToken = default)
 {
     return(await GetDecodedChunkedContentAsync(stream, null, headerStruct, ctsToken).ConfigureAwait(false));
 }