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)); }
public WinHttpResponseHeaderReader(char[] buffer, int startIndex, int length) { CharArrayHelpers.DebugAssertArrayInputs(buffer, startIndex, length); _buffer = buffer; _position = startIndex; _length = length; }
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)); }
/// <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)); }
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)); }
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)); }
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)); } }
/// <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); }
/// <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); }
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); } }
/// <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,
/// <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); }
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); }