public void NullCharactersRejectedInUTF8AndLatin1Mode(bool useLatin1, KnownHeader header) { var headers = new HttpRequestHeaders(encodingSelector: useLatin1 ? _ => Encoding.Latin1 : (Func <string, Encoding>)null); var valueArray = new char[127]; // 64 + 32 + 16 + 8 + 4 + 2 + 1 for (var i = 0; i < valueArray.Length; i++) { valueArray[i] = 'a'; } for (var i = 1; i < valueArray.Length; i++) { // Set non-ascii Latin char that is valid Utf16 when widened; but not a valid utf8 -> utf16 conversion. valueArray[i] = '\0'; string valueString = new string(valueArray); headers.Reset(); Assert.Throws <InvalidOperationException>(() => { var headerName = Encoding.ASCII.GetBytes(header.Name).AsSpan(); var valueSpan = Encoding.ASCII.GetBytes(valueString).AsSpan(); headers.Append(headerName, valueSpan); }); valueArray[i] = 'a'; } }
public void ValueReuseOnlyWhenAllowed(bool reuseValue, KnownHeader header) { const string HeaderValue = "Hello"; var headers = new HttpRequestHeaders(reuseHeaderValues: reuseValue); for (var i = 0; i < 6; i++) { var prevName = ChangeNameCase(header.Name, variant: i); var nextName = ChangeNameCase(header.Name, variant: i + 1); var values = GetHeaderValues(headers, prevName, nextName, HeaderValue, HeaderValue); Assert.Equal(HeaderValue, values.PrevHeaderValue); Assert.NotSame(HeaderValue, values.PrevHeaderValue); Assert.Equal(HeaderValue, values.NextHeaderValue); Assert.NotSame(HeaderValue, values.NextHeaderValue); Assert.Equal(values.PrevHeaderValue, values.NextHeaderValue); if (reuseValue) { // When materialized string is reused previous and new should be the same object Assert.Same(values.PrevHeaderValue, values.NextHeaderValue); } else { // When materialized string is not reused previous and new should be the different objects Assert.NotSame(values.PrevHeaderValue, values.NextHeaderValue); } } }
public void ValueReuseLatin1NotConfusedForUtf16AndStillRejected(bool reuseValue, KnownHeader header) { var headers = new HttpRequestHeaders(reuseHeaderValues: reuseValue); var headerValue = new char[127]; // 64 + 32 + 16 + 8 + 4 + 2 + 1 for (var i = 0; i < headerValue.Length; i++) { headerValue[i] = 'a'; } for (var i = 0; i < headerValue.Length; i++) { // Set non-ascii Latin char that is valid Utf16 when widened; but not a valid utf8 -> utf16 conversion. headerValue[i] = '\u00a3'; for (var mode = 0; mode <= 1; mode++) { string headerValueUtf16Latin1CrossOver; if (mode == 0) { // Full length headerValueUtf16Latin1CrossOver = new string(headerValue); } else { // Truncated length (to ensure different paths from changing lengths in matching) headerValueUtf16Latin1CrossOver = new string(headerValue.AsSpan().Slice(0, i + 1)); } headers.Reset(); var headerName = Encoding.ASCII.GetBytes(header.Name).AsSpan(); var prevSpan = Encoding.UTF8.GetBytes(headerValueUtf16Latin1CrossOver).AsSpan(); headers.Append(headerName, prevSpan, checkForNewlineChars: false); headers.OnHeadersComplete(); var prevHeaderValue = ((IHeaderDictionary)headers)[header.Name].ToString(); Assert.Equal(headerValueUtf16Latin1CrossOver, prevHeaderValue); Assert.NotSame(headerValueUtf16Latin1CrossOver, prevHeaderValue); headers.Reset(); Assert.Throws <InvalidOperationException>(() => { var headerName = Encoding.ASCII.GetBytes(header.Name).AsSpan(); var nextSpan = Encoding.Latin1.GetBytes(headerValueUtf16Latin1CrossOver).AsSpan(); Assert.False(nextSpan.SequenceEqual(Encoding.ASCII.GetBytes(headerValueUtf16Latin1CrossOver))); headers.Append(headerName, nextSpan, checkForNewlineChars: false); }); } // Reset back to Ascii headerValue[i] = 'a'; } }
internal HttpHeader(KnownHeader name, KnownHeader value) { nameString = name.String; NameData = name.String8; valueString = value.String; ValueData = value.String8; }
internal HttpHeader(KnownHeader name, string value) { nameString = name.String; NameData = name.String8; valueString = value.Trim(); ValueData = valueString.GetByteString(); }
static void Validate(string name, KnownHeader h) { Assert.NotNull(h); Assert.Same(h, KnownHeaders.TryGetKnownHeader(name)); Assert.Same(h, h.Descriptor.KnownHeader); Assert.Equal(name, h.Name, StringComparer.OrdinalIgnoreCase); Assert.Equal(name, h.Descriptor.Name, StringComparer.OrdinalIgnoreCase); }
public void Latin1ValuesAcceptedInLatin1ModeButNotReused(bool reuseValue, KnownHeader header) { var headers = new HttpRequestHeaders(reuseHeaderValues: reuseValue, _ => Encoding.Latin1); var headerValue = new char[127]; // 64 + 32 + 16 + 8 + 4 + 2 + 1 for (var i = 0; i < headerValue.Length; i++) { headerValue[i] = 'a'; } for (var i = 0; i < headerValue.Length; i++) { // Set non-ascii Latin char that is valid Utf16 when widened; but not a valid utf8 -> utf16 conversion. headerValue[i] = '\u00a3'; for (var mode = 0; mode <= 1; mode++) { string headerValueUtf16Latin1CrossOver; if (mode == 0) { // Full length headerValueUtf16Latin1CrossOver = new string(headerValue); } else { // Truncated length (to ensure different paths from changing lengths in matching) headerValueUtf16Latin1CrossOver = new string(headerValue.AsSpan().Slice(0, i + 1)); } var headerName = Encoding.ASCII.GetBytes(header.Name).AsSpan(); var latinValueSpan = Encoding.Latin1.GetBytes(headerValueUtf16Latin1CrossOver).AsSpan(); Assert.False(latinValueSpan.SequenceEqual(Encoding.ASCII.GetBytes(headerValueUtf16Latin1CrossOver))); headers.Reset(); headers.Append(headerName, latinValueSpan); headers.OnHeadersComplete(); var parsedHeaderValue1 = ((IHeaderDictionary)headers)[header.Name].ToString(); headers.Reset(); headers.Append(headerName, latinValueSpan); headers.OnHeadersComplete(); var parsedHeaderValue2 = ((IHeaderDictionary)headers)[header.Name].ToString(); Assert.Equal(headerValueUtf16Latin1CrossOver, parsedHeaderValue1); Assert.Equal(parsedHeaderValue1, parsedHeaderValue2); Assert.NotSame(parsedHeaderValue1, parsedHeaderValue2); } // Reset back to Ascii headerValue[i] = 'a'; } }
public void MultiValueReuseEmptyAfterReset(bool reuseValue, KnownHeader header) { const string HeaderValue1 = "Hello1"; const string HeaderValue2 = "Hello2"; var headers = new HttpRequestHeaders(reuseHeaderValues: reuseValue); var headerName = Encoding.ASCII.GetBytes(header.Name).AsSpan(); var prevSpan1 = Encoding.UTF8.GetBytes(HeaderValue1).AsSpan(); var prevSpan2 = Encoding.UTF8.GetBytes(HeaderValue2).AsSpan(); headers.Append(headerName, prevSpan1); headers.Append(headerName, prevSpan2); headers.OnHeadersComplete(); var prevHeaderValue = ((IHeaderDictionary)headers)[header.Name]; Assert.Equal(2, prevHeaderValue.Count); Assert.NotEqual(string.Empty, prevHeaderValue.ToString()); Assert.Single(headers); var count = headers.Count; Assert.Equal(1, count); headers.Reset(); // Empty after reset var nextHeaderValue = ((IHeaderDictionary)headers)[header.Name].ToString(); Assert.NotNull(nextHeaderValue); Assert.Equal(string.Empty, nextHeaderValue); Assert.Empty(headers); count = headers.Count; Assert.Equal(0, count); headers.OnHeadersComplete(); // Still empty after complete nextHeaderValue = ((IHeaderDictionary)headers)[header.Name].ToString(); Assert.NotNull(nextHeaderValue); Assert.Equal(string.Empty, nextHeaderValue); Assert.Empty(headers); count = headers.Count; Assert.Equal(0, count); }
public void ValueReuseEmptyAfterReset(bool reuseValue, KnownHeader header) { const string HeaderValue = "Hello"; var headers = new HttpRequestHeaders(reuseHeaderValues: reuseValue); var headerName = Encoding.ASCII.GetBytes(header.Name).AsSpan(); var prevSpan = Encoding.UTF8.GetBytes(HeaderValue).AsSpan(); headers.Append(headerName, prevSpan, checkForNewlineChars: false); headers.OnHeadersComplete(); var prevHeaderValue = ((IHeaderDictionary)headers)[header.Name].ToString(); Assert.NotNull(prevHeaderValue); Assert.NotEqual(string.Empty, prevHeaderValue); Assert.Equal(HeaderValue, prevHeaderValue); Assert.NotSame(HeaderValue, prevHeaderValue); Assert.Single(headers); var count = headers.Count; Assert.Equal(1, count); headers.Reset(); // Empty after reset var nextHeaderValue = ((IHeaderDictionary)headers)[header.Name].ToString(); Assert.NotNull(nextHeaderValue); Assert.Equal(string.Empty, nextHeaderValue); Assert.NotEqual(HeaderValue, nextHeaderValue); Assert.Empty(headers); count = headers.Count; Assert.Equal(0, count); headers.OnHeadersComplete(); // Still empty after complete nextHeaderValue = ((IHeaderDictionary)headers)[header.Name].ToString(); Assert.NotNull(nextHeaderValue); Assert.Equal(string.Empty, nextHeaderValue); Assert.NotEqual(HeaderValue, nextHeaderValue); Assert.Empty(headers); count = headers.Count; Assert.Equal(0, count); }
public void ValueReuseMissingValuesClear(bool reuseValue, KnownHeader header) { const string HeaderValue1 = "Hello1"; var headers = new HttpRequestHeaders(reuseHeaderValues: reuseValue); for (var i = 0; i < 6; i++) { var prevName = ChangeNameCase(header.Name, variant: i); var nextName = ChangeNameCase(header.Name, variant: i + 1); var values = GetHeaderValues(headers, prevName, nextName, HeaderValue1, nextValue: null); Assert.Equal(HeaderValue1, values.PrevHeaderValue); Assert.NotSame(HeaderValue1, values.PrevHeaderValue); Assert.Equal(string.Empty, values.NextHeaderValue); Assert.NotEqual(values.PrevHeaderValue, values.NextHeaderValue); } }
public void ValueReuseChangedValuesOverwrite(bool reuseValue, KnownHeader header) { const string HeaderValue1 = "Hello1"; const string HeaderValue2 = "Hello2"; var headers = new HttpRequestHeaders(reuseHeaderValues: reuseValue); for (var i = 0; i < 6; i++) { var prevName = ChangeNameCase(header.Name, variant: i); var nextName = ChangeNameCase(header.Name, variant: i + 1); var values = GetHeaderValues(headers, prevName, nextName, HeaderValue1, HeaderValue2); Assert.Equal(HeaderValue1, values.PrevHeaderValue); Assert.NotSame(HeaderValue1, values.PrevHeaderValue); Assert.Equal(HeaderValue2, values.NextHeaderValue); Assert.NotSame(HeaderValue2, values.NextHeaderValue); Assert.NotEqual(values.PrevHeaderValue, values.NextHeaderValue); } }
public void ValueReuseNeverWhenNotAscii(bool reuseValue, KnownHeader header) { const string HeaderValue = "Hello \u03a0"; var headers = new HttpRequestHeaders(reuseHeaderValues: reuseValue); for (var i = 0; i < 6; i++) { var prevName = ChangeNameCase(header.Name, variant: i); var nextName = ChangeNameCase(header.Name, variant: i + 1); var values = GetHeaderValues(headers, prevName, nextName, HeaderValue, HeaderValue); Assert.Equal(HeaderValue, values.PrevHeaderValue); Assert.NotSame(HeaderValue, values.PrevHeaderValue); Assert.Equal(HeaderValue, values.NextHeaderValue); Assert.NotSame(HeaderValue, values.NextHeaderValue); Assert.Equal(values.PrevHeaderValue, values.NextHeaderValue); Assert.NotSame(values.PrevHeaderValue, values.NextHeaderValue); } }
internal void SetValue(KnownHeader value) { valueString = value.String; ValueData = value.String8; }
// adapted from Header serialization code in Http2Connection.cs private static Memory <byte> HPackEncode(HttpHeaders headers) { var buffer = new ArrayBuffer(4); FillAvailableSpaceWithOnes(buffer); foreach (KeyValuePair <HeaderDescriptor, string[]> header in headers.GetHeaderDescriptorsAndValues()) { KnownHeader knownHeader = header.Key.KnownHeader; if (knownHeader != null) { // For all other known headers, send them via their pre-encoded name and the associated value. WriteBytes(knownHeader.Http2EncodedName); string separator = null; if (header.Value.Length > 1) { HttpHeaderParser parser = header.Key.Parser; if (parser != null && parser.SupportsMultipleValues) { separator = parser.Separator; } else { separator = HttpHeaderParser.DefaultSeparator; } } WriteLiteralHeaderValues(header.Value, separator); } else { // The header is not known: fall back to just encoding the header name and value(s). WriteLiteralHeader(header.Key.Name, header.Value); } } return(buffer.ActiveMemory); void WriteBytes(ReadOnlySpan <byte> bytes) { if (bytes.Length > buffer.AvailableLength) { buffer.EnsureAvailableSpace(bytes.Length); FillAvailableSpaceWithOnes(buffer); } bytes.CopyTo(buffer.AvailableSpan); buffer.Commit(bytes.Length); } void WriteLiteralHeaderValues(string[] values, string separator) { int bytesWritten; while (!HPackEncoder.EncodeStringLiterals(values, separator, buffer.AvailableSpan, out bytesWritten)) { buffer.EnsureAvailableSpace(buffer.AvailableLength + 1); FillAvailableSpaceWithOnes(buffer); } buffer.Commit(bytesWritten); } void WriteLiteralHeader(string name, string[] values) { int bytesWritten; while (!HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, values, HttpHeaderParser.DefaultSeparator, buffer.AvailableSpan, out bytesWritten)) { buffer.EnsureAvailableSpace(buffer.AvailableLength + 1); FillAvailableSpaceWithOnes(buffer); } buffer.Commit(bytesWritten); } // force issues related to buffer not being zeroed out void FillAvailableSpaceWithOnes(ArrayBuffer buffer) => buffer.AvailableSpan.Fill(0xff); }
// adapted from Header serialization code in Http2Connection.cs private static Memory <byte> HPackEncode(HttpHeaders headers, Encoding?valueEncoding) { var buffer = new ArrayBuffer(4); FillAvailableSpaceWithOnes(buffer); string[] headerValues = Array.Empty <string>(); foreach (HeaderEntry header in headers.GetEntries()) { int headerValuesCount = HttpHeaders.GetStoreValuesIntoStringArray(header.Key, header.Value, ref headerValues); Assert.InRange(headerValuesCount, 0, int.MaxValue); ReadOnlySpan <string> headerValuesSpan = headerValues.AsSpan(0, headerValuesCount); KnownHeader knownHeader = header.Key.KnownHeader; if (knownHeader != null) { // For all other known headers, send them via their pre-encoded name and the associated value. WriteBytes(knownHeader.Http2EncodedName); string separator = null; if (headerValuesSpan.Length > 1) { HttpHeaderParser parser = header.Key.Parser; if (parser != null && parser.SupportsMultipleValues) { separator = parser.Separator; } else { separator = HttpHeaderParser.DefaultSeparator; } } WriteLiteralHeaderValues(headerValuesSpan, separator); } else { // The header is not known: fall back to just encoding the header name and value(s). WriteLiteralHeader(header.Key.Name, headerValuesSpan); } } return(buffer.ActiveMemory); void WriteBytes(ReadOnlySpan <byte> bytes) { if (bytes.Length > buffer.AvailableLength) { buffer.EnsureAvailableSpace(bytes.Length); FillAvailableSpaceWithOnes(buffer); } bytes.CopyTo(buffer.AvailableSpan); buffer.Commit(bytes.Length); } void WriteLiteralHeaderValues(ReadOnlySpan <string> values, string separator) { int bytesWritten; while (!HPackEncoder.EncodeStringLiterals(values, separator, valueEncoding, buffer.AvailableSpan, out bytesWritten)) { buffer.EnsureAvailableSpace(buffer.AvailableLength + 1); FillAvailableSpaceWithOnes(buffer); } buffer.Commit(bytesWritten); } void WriteLiteralHeader(string name, ReadOnlySpan <string> values) { int bytesWritten; while (!HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, values, HttpHeaderParser.DefaultSeparator, valueEncoding, buffer.AvailableSpan, out bytesWritten)) { buffer.EnsureAvailableSpace(buffer.AvailableLength + 1); FillAvailableSpaceWithOnes(buffer); } buffer.Commit(bytesWritten); } // force issues related to buffer not being zeroed out void FillAvailableSpaceWithOnes(ArrayBuffer buffer) => buffer.AvailableSpan.Fill(0xff); }