public static string GetHeaderValue(string name, char[] array, int startIndex, int length)
        {
            Debug.Assert(name != null);
            CharArrayHelpers.DebugAssertArrayInputs(array, startIndex, length);

            if (length == 0)
            {
                return(string.Empty);
            }

            // If it's a known header value, use the known value instead of allocating a new string.

            // Do a really quick reference equals check to see if name is the same object as
            // HttpKnownHeaderNames.ContentEncoding, in which case the value is very likely to
            // be either "gzip" or "deflate".
            if (ReferenceEquals(name, ContentEncoding))
            {
                if (CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(Gzip, array, startIndex, length))
                {
                    return(Gzip);
                }
                else if (CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(Deflate, array, startIndex, length))
                {
                    return(Deflate);
                }
            }

            return(new string(array, startIndex, length));
        }
Exemplo n.º 2
0
        public WinHttpResponseHeaderReader(char[] buffer, int startIndex, int length)
        {
            CharArrayHelpers.DebugAssertArrayInputs(buffer, startIndex, length);

            _buffer   = buffer;
            _position = startIndex;
            _length   = length;
        }
Exemplo n.º 3
0
        public void EqualsOrdinalAsciiIgnoreCase_ComparingVariousInputsBothWays_ReturnsExpected(string leftString, string rightString, bool expected)
        {
            char[] left  = leftString.ToCharArray();
            char[] right = rightString.ToCharArray();

            Assert.Equal(expected, CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(leftString, right, 0, right.Length));
            Assert.Equal(expected, CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(rightString, left, 0, left.Length));
        }
Exemplo n.º 4
0
        /// <summary>
        /// Gets a known header name string from a matching char[] array segment, using an ordinal comparison.
        /// Used to avoid allocating new strings for known header names.
        /// </summary>
        public static bool TryGetHeaderName(char[] array, int startIndex, int length, out string name)
        {
            CharArrayHelpers.DebugAssertArrayInputs(array, startIndex, length);

            return(TryGetHeaderName(
                       array, startIndex, length,
                       (arr, index) => arr[index],
                       (known, arr, start, len) => CharArrayHelpers.EqualsOrdinal(known, arr, start, len),
                       out name));
        }
        /// <summary>
        /// Gets a known header name string from a matching char[] array segment, using a case-sensitive
        /// ordinal comparison. Used to avoid allocating new strings for known header names.
        /// </summary>
        public static bool TryGetHeaderName(char[] array, int startIndex, int length, out string name)
        {
            CharArrayHelpers.DebugAssertArrayInputs(array, startIndex, length);

            return(TryGetHeaderName(
                       array, startIndex, length,
                       (arr, index) => arr[index],
                       (known, arr, start, len) => known.AsSpan().SequenceEqual(arr.AsSpan(start, len)),
                       out name));
        }
Exemplo n.º 6
0
        public void Trim_VariousInputs_ReturnsExpectedSubstring(string valueString, string expected)
        {
            char[] value      = valueString.ToCharArray();
            int    startIndex = 0;
            int    length     = value.Length;

            CharArrayHelpers.Trim(value, ref startIndex, ref length);

            Assert.Equal(expected, new string(value, startIndex, length));
        }
Exemplo n.º 7
0
        private static string GetReasonPhrase(HttpStatusCode statusCode, char[] buffer, int bufferLength)
        {
            CharArrayHelpers.DebugAssertArrayInputs(buffer, 0, bufferLength);
            Debug.Assert(bufferLength > 0);

            // If it's a known reason phrase, use the known reason phrase instead of allocating a new string.

            string knownReasonPhrase = HttpStatusDescription.Get(statusCode);

            return((knownReasonPhrase != null && CharArrayHelpers.EqualsOrdinal(knownReasonPhrase, buffer, 0, bufferLength)) ?
                   knownReasonPhrase :
                   new string(buffer, 0, bufferLength));
        }
Exemplo n.º 8
0
        public void EqualsOrdinalAsciiIgnoreCase_ComparingLowerAndUpperCaseAsciiChars_ReturnsTrue()
        {
            for (char c = '\0'; c <= 127; c++)
            {
                string lowerString    = c.ToString();
                string upperString    = char.ToUpperInvariant(c).ToString();
                char[] lowerCharArray = new char[] { c };
                char[] upperCharArray = new char[] { char.ToUpperInvariant(c) };

                Assert.True(CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(lowerString, lowerCharArray, 0, 1));
                Assert.True(CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(upperString, lowerCharArray, 0, 1));
                Assert.True(CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(lowerString, upperCharArray, 0, 1));
                Assert.True(CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(upperString, upperCharArray, 0, 1));
            }
        }
Exemplo n.º 9
0
        /// <summary>
        /// Reads a header line.
        /// Empty header lines are skipped, as are malformed header lines that are missing a colon character.
        /// </summary>
        /// <returns>true if the next header was read successfully, or false if all characters have been read.</returns>
        public bool ReadHeader([NotNullWhen(true)] out string?name, [NotNullWhen(true)] out string?value)
        {
            int startIndex;
            int length;

            while (ReadLine(out startIndex, out length))
            {
                // Skip empty lines.
                if (length == 0)
                {
                    continue;
                }

                int colonIndex = Array.IndexOf(_buffer, ':', startIndex, length);

                // Skip malformed header lines that are missing the colon character.
                if (colonIndex == -1)
                {
                    continue;
                }

                int nameLength = colonIndex - startIndex;

                // If it's a known header name, use the known name instead of allocating a new string.
                if (!HttpKnownHeaderNames.TryGetHeaderName(_buffer, startIndex, nameLength, out name))
                {
                    name = new string(_buffer, startIndex, nameLength);
                }

                // Normalize header value by trimming whitespace.
                int valueStartIndex = colonIndex + 1;
                int valueLength     = startIndex + length - colonIndex - 1;
                CharArrayHelpers.Trim(_buffer, ref valueStartIndex, ref valueLength);

                value = HttpKnownHeaderNames.GetHeaderValue(name, _buffer, valueStartIndex, valueLength);

                return(true);
            }

            name  = null;
            value = null;
            return(false);
        }
Exemplo n.º 10
0
        /// <summary>
        /// Returns true if <paramref name="known"/> matches the <paramref name="key"/> char[] array segment,
        /// using an ordinal comparison.
        /// </summary>
        private static bool TryMatch(string known, char[] key, int startIndex, int length, out string name)
        {
            Debug.Assert(known != null);
            Debug.Assert(known.Length > 0);
            CharArrayHelpers.DebugAssertArrayInputs(key, startIndex, length);

            // The lengths should be equal because this method is only called
            // from within a "switch (length) { ... }".
            Debug.Assert(known.Length == length);

            if (CharArrayHelpers.EqualsOrdinal(known, key, startIndex, length))
            {
                name = known;
                return(true);
            }

            name = null;
            return(false);
        }
Exemplo n.º 11
0
        public static HttpResponseMessage CreateResponseMessage(
            WinHttpRequestState state,
            bool doManualDecompressionCheck)
        {
            HttpRequestMessage request         = state.RequestMessage;
            SafeWinHttpHandle  requestHandle   = state.RequestHandle;
            CookieUsePolicy    cookieUsePolicy = state.Handler.CookieUsePolicy;
            CookieContainer    cookieContainer = state.Handler.CookieContainer;
            var  response             = new HttpResponseMessage();
            bool stripEncodingHeaders = false;

            // Create a single buffer to use for all subsequent WinHttpQueryHeaders string interop calls.
            // This buffer is the length needed for WINHTTP_QUERY_RAW_HEADERS_CRLF, which includes the status line
            // and all headers separated by CRLF, so it should be large enough for any individual status line or header queries.
            int bufferLength = GetResponseHeaderCharBufferLength(requestHandle, Interop.WinHttp.WINHTTP_QUERY_RAW_HEADERS_CRLF);

            char[] buffer = ArrayPool <char> .Shared.Rent(bufferLength);

            try
            {
                // Get HTTP version, status code, reason phrase from the response headers.

                if (IsResponseHttp2(requestHandle))
                {
                    response.Version = WinHttpHandler.HttpVersion20;
                }
                else
                {
                    int versionLength = GetResponseHeader(requestHandle, Interop.WinHttp.WINHTTP_QUERY_VERSION, buffer);
                    response.Version =
                        CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase("HTTP/1.1", buffer, 0, versionLength) ? HttpVersionInternal.Version11 :
                        CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase("HTTP/1.0", buffer, 0, versionLength) ? HttpVersionInternal.Version10 :
                        WinHttpHandler.HttpVersionUnknown;
                }

                response.StatusCode = (HttpStatusCode)GetResponseHeaderNumberInfo(
                    requestHandle,
                    Interop.WinHttp.WINHTTP_QUERY_STATUS_CODE);

                int reasonPhraseLength = GetResponseHeader(requestHandle, Interop.WinHttp.WINHTTP_QUERY_STATUS_TEXT, buffer);
                response.ReasonPhrase = reasonPhraseLength > 0 ?
                                        GetReasonPhrase(response.StatusCode, buffer, reasonPhraseLength) :
                                        string.Empty;

                // Create response stream and wrap it in a StreamContent object.
                var responseStream = new WinHttpResponseStream(requestHandle, state);
                state.RequestHandle = null; // ownership successfully transfered to WinHttpResponseStram.
                Stream decompressedStream = responseStream;

                if (doManualDecompressionCheck)
                {
                    int contentEncodingStartIndex = 0;
                    int contentEncodingLength     = GetResponseHeader(
                        requestHandle,
                        Interop.WinHttp.WINHTTP_QUERY_CONTENT_ENCODING,
                        buffer);

                    CharArrayHelpers.Trim(buffer, ref contentEncodingStartIndex, ref contentEncodingLength);

                    if (contentEncodingLength > 0)
                    {
                        if (CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(
                                EncodingNameGzip, buffer, contentEncodingStartIndex, contentEncodingLength))
                        {
                            decompressedStream   = new GZipStream(responseStream, CompressionMode.Decompress);
                            stripEncodingHeaders = true;
                        }
                        else if (CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(
                                     EncodingNameDeflate, buffer, contentEncodingStartIndex, contentEncodingLength))
                        {
                            decompressedStream   = new DeflateStream(responseStream, CompressionMode.Decompress);
                            stripEncodingHeaders = true;
                        }
                    }
                }

                response.Content        = new NoWriteNoSeekStreamContent(decompressedStream);
                response.RequestMessage = request;

                // Parse raw response headers and place them into response message.
                ParseResponseHeaders(requestHandle, response, buffer, stripEncodingHeaders);

                if (response.RequestMessage.Method != HttpMethod.Head)
                {
                    state.ExpectedBytesToRead = response.Content.Headers.ContentLength;
                }

                return(response);
            }
            finally
            {
                ArrayPool <char> .Shared.Return(buffer);
            }
        }
Exemplo n.º 12
0
        /// <summary>
        /// Gets a known header name string from a matching char[] array segment, using a case-sensitive
        /// ordinal comparison. Used to avoid allocating new strings for known header names.
        /// </summary>
        public static bool TryGetHeaderName(char[] array, int startIndex, int length, [NotNullWhen(true)] out string?name)
        {
            CharArrayHelpers.DebugAssertArrayInputs(array, startIndex, length);

            return(TryGetHeaderName(
                       array, startIndex, length,
Exemplo n.º 13
0
        /// <summary>
        /// Gets a known header name string from a matching char[] array segment, using an ordinal comparison.
        /// Used to avoid allocating new strings for known header names.
        /// </summary>
        public static bool TryGetHeaderName(char[] key, int startIndex, int length, out string name)
        {
            CharArrayHelpers.DebugAssertArrayInputs(key, startIndex, length);

            // When adding a new constant, add it to HttpKnownHeaderNames.cs as well.

            // The lookup works as follows: first switch on the length of the passed-in char[] array segment.
            //
            //  - If there is only one known header of that length and the char[] array segment matches that
            //    known header, set it as the out param and return true.
            //
            //  - If there are more than one known headers of that length, switch on a unique char from that
            //    set of same-length known headers. Typically this will be the first char, but some sets of
            //    same-length known headers do not have unique chars in the first position, so a char in a
            //    position further in the strings is used. If the char[] array segment matches one of the
            //    known headers, set it as the out param and return true.
            //
            //  - Otherwise, set the out param to null and return false.

            switch (length)
            {
            case 2:
                return(TryMatch(TE, key, startIndex, length, out name));    // TE

            case 3:
                switch (key[startIndex])
                {
                case 'A': return(TryMatch(Age, key, startIndex, length, out name));        // [A]ge

                case 'P': return(TryMatch(P3P, key, startIndex, length, out name));        // [P]3P

                case 'V': return(TryMatch(Via, key, startIndex, length, out name));        // [V]ia
                }
                break;

            case 4:
                switch (key[startIndex])
                {
                case 'D': return(TryMatch(Date, key, startIndex, length, out name));        // [D]ate

                case 'E': return(TryMatch(ETag, key, startIndex, length, out name));        // [E]Tag

                case 'F': return(TryMatch(From, key, startIndex, length, out name));        // [F]rom

                case 'H': return(TryMatch(Host, key, startIndex, length, out name));        // [H]ost

                case 'V': return(TryMatch(Vary, key, startIndex, length, out name));        // [V]ary
                }
                break;

            case 5:
                switch (key[startIndex])
                {
                case 'A': return(TryMatch(Allow, key, startIndex, length, out name));        // [A]llow

                case 'R': return(TryMatch(Range, key, startIndex, length, out name));        // [R]ange
                }
                break;

            case 6:
                switch (key[startIndex])
                {
                case 'A': return(TryMatch(Accept, key, startIndex, length, out name));        // [A]ccept

                case 'C': return(TryMatch(Cookie, key, startIndex, length, out name));        // [C]ookie

                case 'E': return(TryMatch(Expect, key, startIndex, length, out name));        // [E]xpect

                case 'O': return(TryMatch(Origin, key, startIndex, length, out name));        // [O]rigin

                case 'P': return(TryMatch(Pragma, key, startIndex, length, out name));        // [P]ragma

                case 'S': return(TryMatch(Server, key, startIndex, length, out name));        // [S]erver
                }
                break;

            case 7:
                switch (key[startIndex])
                {
                case 'C': return(TryMatch(Cookie2, key, startIndex, length, out name));        // [C]ookie2

                case 'E': return(TryMatch(Expires, key, startIndex, length, out name));        // [E]xpires

                case 'R': return(TryMatch(Referer, key, startIndex, length, out name));        // [R]eferer

                case 'T': return(TryMatch(Trailer, key, startIndex, length, out name));        // [T]railer

                case 'U': return(TryMatch(Upgrade, key, startIndex, length, out name));        // [U]pgrade

                case 'W': return(TryMatch(Warning, key, startIndex, length, out name));        // [W]arning
                }
                break;

            case 8:
                switch (key[startIndex + 3])
                {
                case 'M': return(TryMatch(IfMatch, key, startIndex, length, out name));         // If-[M]atch

                case 'R': return(TryMatch(IfRange, key, startIndex, length, out name));         // If-[R]ange

                case 'a': return(TryMatch(Location, key, startIndex, length, out name));        // Loc[a]tion
                }
                break;

            case 10:
                switch (key[startIndex])
                {
                case 'C': return(TryMatch(Connection, key, startIndex, length, out name));        // [C]onnection

                case 'K': return(TryMatch(KeepAlive, key, startIndex, length, out name));         // [K]eep-Alive

                case 'S': return(TryMatch(SetCookie, key, startIndex, length, out name));         // [S]et-Cookie

                case 'U': return(TryMatch(UserAgent, key, startIndex, length, out name));         // [U]ser-Agent
                }
                break;

            case 11:
                switch (key[startIndex])
                {
                case 'C': return(TryMatch(ContentMD5, key, startIndex, length, out name));        // [C]ontent-MD5

                case 'R': return(TryMatch(RetryAfter, key, startIndex, length, out name));        // [R]etry-After

                case 'S': return(TryMatch(SetCookie2, key, startIndex, length, out name));        // [S]et-Cookie2
                }
                break;

            case 12:
                switch (key[startIndex])
                {
                case 'C': return(TryMatch(ContentType, key, startIndex, length, out name));        // [C]ontent-Type

                case 'M': return(TryMatch(MaxForwards, key, startIndex, length, out name));        // [M]ax-Forwards

                case 'X': return(TryMatch(XPoweredBy, key, startIndex, length, out name));         // [X]-Powered-By
                }
                break;

            case 13:
                switch (key[startIndex + 6])
                {
                case '-': return(TryMatch(AcceptRanges, key, startIndex, length, out name));         // Accept[-]Ranges

                case 'i': return(TryMatch(Authorization, key, startIndex, length, out name));        // Author[i]zation

                case 'C': return(TryMatch(CacheControl, key, startIndex, length, out name));         // Cache-[C]ontrol

                case 't': return(TryMatch(ContentRange, key, startIndex, length, out name));         // Conten[t]-Range

                case 'e': return(TryMatch(IfNoneMatch, key, startIndex, length, out name));          // If-Non[e]-Match

                case 'o': return(TryMatch(LastModified, key, startIndex, length, out name));         // Last-M[o]dified
                }
                break;

            case 14:
                switch (key[startIndex])
                {
                case 'A': return(TryMatch(AcceptCharset, key, startIndex, length, out name));        // [A]ccept-Charset

                case 'C': return(TryMatch(ContentLength, key, startIndex, length, out name));        // [C]ontent-Length
                }
                break;

            case 15:
                switch (key[startIndex + 7])
                {
                case 'E': return(TryMatch(AcceptEncoding, key, startIndex, length, out name));        // Accept-[E]ncoding

                case 'L': return(TryMatch(AcceptLanguage, key, startIndex, length, out name));        // Accept-[L]anguage
                }
                break;

            case 16:
                switch (key[startIndex + 11])
                {
                case 'o': return(TryMatch(ContentEncoding, key, startIndex, length, out name));        // Content-Enc[o]ding

                case 'g': return(TryMatch(ContentLanguage, key, startIndex, length, out name));        // Content-Lan[g]uage

                case 'a': return(TryMatch(ContentLocation, key, startIndex, length, out name));        // Content-Loc[a]tion

                case 'c': return(TryMatch(ProxyConnection, key, startIndex, length, out name));        // Proxy-Conne[c]tion

                case 'i': return(TryMatch(WWWAuthenticate, key, startIndex, length, out name));        // WWW-Authent[i]cate

                case 'r': return(TryMatch(XAspNetVersion, key, startIndex, length, out name));         // X-AspNet-Ve[r]sion
                }
                break;

            case 17:
                switch (key[startIndex])
                {
                case 'I': return(TryMatch(IfModifiedSince, key, startIndex, length, out name));         // [I]f-Modified-Since

                case 'S': return(TryMatch(SecWebSocketKey, key, startIndex, length, out name));         // [S]ec-WebSocket-Key

                case 'T': return(TryMatch(TransferEncoding, key, startIndex, length, out name));        // [T]ransfer-Encoding
                }
                break;

            case 18:
                return(TryMatch(ProxyAuthenticate, key, startIndex, length, out name));    // Proxy-Authenticate

            case 19:
                switch (key[startIndex])
                {
                case 'C': return(TryMatch(ContentDisposition, key, startIndex, length, out name));        // [C]ontent-Disposition

                case 'I': return(TryMatch(IfUnmodifiedSince, key, startIndex, length, out name));         // [I]f-Unmodified-Since

                case 'P': return(TryMatch(ProxyAuthorization, key, startIndex, length, out name));        // [P]roxy-Authorization
                }
                break;

            case 20:
                return(TryMatch(SecWebSocketAccept, key, startIndex, length, out name));    // Sec-WebSocket-Accept

            case 21:
                return(TryMatch(SecWebSocketVersion, key, startIndex, length, out name));    // Sec-WebSocket-Version

            case 22:
                return(TryMatch(SecWebSocketProtocol, key, startIndex, length, out name));    // Sec-WebSocket-Protocol

            case 24:
                return(TryMatch(SecWebSocketExtensions, key, startIndex, length, out name));    // Sec-WebSocket-Extensions
            }

            name = null;
            return(false);
        }
Exemplo n.º 14
0
        public static HttpResponseMessage CreateResponseMessage(
            WinHttpRequestState state,
            bool doManualDecompressionCheck)
        {
            HttpRequestMessage request         = state.RequestMessage;
            SafeWinHttpHandle  requestHandle   = state.RequestHandle;
            CookieUsePolicy    cookieUsePolicy = state.Handler.CookieUsePolicy;
            CookieContainer    cookieContainer = state.Handler.CookieContainer;
            var  response             = new HttpResponseMessage();
            bool stripEncodingHeaders = false;

            // Create a single buffer to use for all subsequent WinHttpQueryHeaders string interop calls.
            // This buffer is the length needed for WINHTTP_QUERY_RAW_HEADERS_CRLF, which includes the status line
            // and all headers separated by CRLF, so it should be large enough for any individual status line or header queries.
            int bufferLength = GetResponseHeaderCharBufferLength(requestHandle, Interop.WinHttp.WINHTTP_QUERY_RAW_HEADERS_CRLF);

            char[] buffer = new char[bufferLength];

            // Get HTTP version, status code, reason phrase from the response headers.

            int versionLength = GetResponseHeader(requestHandle, Interop.WinHttp.WINHTTP_QUERY_VERSION, buffer);

            response.Version =
                CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase("HTTP/1.1", buffer, 0, versionLength) ? HttpVersion.Version11 :
                CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase("HTTP/1.0", buffer, 0, versionLength) ? HttpVersion.Version10 :
                HttpVersion.Unknown;

            response.StatusCode = (HttpStatusCode)GetResponseHeaderNumberInfo(
                requestHandle,
                Interop.WinHttp.WINHTTP_QUERY_STATUS_CODE);

            int reasonPhraseLength = GetResponseHeader(requestHandle, Interop.WinHttp.WINHTTP_QUERY_STATUS_TEXT, buffer);

            response.ReasonPhrase = reasonPhraseLength > 0 ?
                                    GetReasonPhrase(response.StatusCode, buffer, reasonPhraseLength) :
                                    string.Empty;

            // Create response stream and wrap it in a StreamContent object.
            var responseStream = new WinHttpResponseStream(requestHandle, state);

            state.RequestHandle = null; // ownership successfully transfered to WinHttpResponseStram.
            Stream decompressedStream = responseStream;

            if (doManualDecompressionCheck)
            {
                int contentEncodingStartIndex = 0;
                int contentEncodingLength     = GetResponseHeader(
                    requestHandle,
                    Interop.WinHttp.WINHTTP_QUERY_CONTENT_ENCODING,
                    buffer);

                CharArrayHelpers.Trim(buffer, ref contentEncodingStartIndex, ref contentEncodingLength);

                if (contentEncodingLength > 0)
                {
                    if (CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(
                            EncodingNameGzip, buffer, contentEncodingStartIndex, contentEncodingLength))
                    {
                        decompressedStream   = new GZipStream(responseStream, CompressionMode.Decompress);
                        stripEncodingHeaders = true;
                    }
                    else if (CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(
                                 EncodingNameDeflate, buffer, contentEncodingStartIndex, contentEncodingLength))
                    {
                        decompressedStream   = new DeflateStream(responseStream, CompressionMode.Decompress);
                        stripEncodingHeaders = true;
                    }
                }
            }

#if HTTP_DLL
            var content = new StreamContent(decompressedStream, state.CancellationToken);
#else
            // TODO: Issue https://github.com/dotnet/corefx/issues/9071
            // We'd like to be able to pass state.CancellationToken into the StreamContent so that its
            // SerializeToStreamAsync method can use it, but that ctor isn't public, nor is there a
            // SerializeToStreamAsync override that takes a CancellationToken.
            var content = new StreamContent(decompressedStream);
#endif

            response.Content        = content;
            response.RequestMessage = request;

            // Parse raw response headers and place them into response message.
            ParseResponseHeaders(requestHandle, response, buffer, stripEncodingHeaders);

            if (response.RequestMessage.Method != HttpMethod.Head)
            {
                state.ExpectedBytesToRead = response.Content.Headers.ContentLength;
            }

            return(response);
        }