Fix "Literal Header Field With Name Reference" parsing for Status code. Add tests for QPACK-ed and not QPACK-ed status receiving for all status codes (except informational).
Fix QPackStaticTable.HeaderLookup to comply with spec (the order was off, so lookup didn't work correctly in some cases).
Fixes #55988
CreateHeaderField(":authority", ""), // 0
CreateHeaderField(":path", "/"), // 1
CreateHeaderField("age", "0"), // 2
- CreateHeaderField("content-disposition", ""),
- CreateHeaderField("content-length", "0"),
- CreateHeaderField("cookie", ""),
- CreateHeaderField("date", ""),
- CreateHeaderField("etag", ""),
- CreateHeaderField("if-modified-since", ""),
- CreateHeaderField("if-none-match", ""),
+ CreateHeaderField("content-disposition", ""), //3
+ CreateHeaderField("content-length", "0"), // 4
+ CreateHeaderField("cookie", ""), // 5
+ CreateHeaderField("date", ""), // 6
+ CreateHeaderField("etag", ""), // 7
+ CreateHeaderField("if-modified-since", ""), // 8
+ CreateHeaderField("if-none-match", ""), // 9
CreateHeaderField("last-modified", ""), // 10
- CreateHeaderField("link", ""),
- CreateHeaderField("location", ""),
- CreateHeaderField("referer", ""),
- CreateHeaderField("set-cookie", ""),
- CreateHeaderField(":method", "CONNECT"),
- CreateHeaderField(":method", "DELETE"),
- CreateHeaderField(":method", "GET"),
- CreateHeaderField(":method", "HEAD"),
- CreateHeaderField(":method", "OPTIONS"),
+ CreateHeaderField("link", ""), // 11
+ CreateHeaderField("location", ""), // 12
+ CreateHeaderField("referer", ""), // 13
+ CreateHeaderField("set-cookie", ""), // 14
+ CreateHeaderField(":method", "CONNECT"), // 15
+ CreateHeaderField(":method", "DELETE"), // 16
+ CreateHeaderField(":method", "GET"), // 17
+ CreateHeaderField(":method", "HEAD"), // 18
+ CreateHeaderField(":method", "OPTIONS"), // 19
CreateHeaderField(":method", "POST"), // 20
- CreateHeaderField(":method", "PUT"),
- CreateHeaderField(":scheme", "http"),
- CreateHeaderField(":scheme", "https"),
- CreateHeaderField(":status", "103"),
- CreateHeaderField(":status", "200"),
- CreateHeaderField(":status", "304"),
- CreateHeaderField(":status", "404"),
- CreateHeaderField(":status", "503"),
- CreateHeaderField("accept", "*/*"),
+ CreateHeaderField(":method", "PUT"), // 21
+ CreateHeaderField(":scheme", "http"), // 22
+ CreateHeaderField(":scheme", "https"), // 23
+ CreateHeaderField(":status", "103"), // 24
+ CreateHeaderField(":status", "200"), // 25
+ CreateHeaderField(":status", "304"), // 26
+ CreateHeaderField(":status", "404"), // 27
+ CreateHeaderField(":status", "503"), // 28
+ CreateHeaderField("accept", "*/*"), //29
CreateHeaderField("accept", "application/dns-message"), // 30
- CreateHeaderField("accept-encoding", "gzip, deflate, br"),
- CreateHeaderField("accept-ranges", "bytes"),
- CreateHeaderField("access-control-allow-headers", "cache-control"),
- CreateHeaderField("access-control-allow-origin", "content-type"),
- CreateHeaderField("access-control-allow-origin", "*"),
- CreateHeaderField("cache-control", "max-age=0"),
- CreateHeaderField("cache-control", "max-age=2592000"),
- CreateHeaderField("cache-control", "max-age=604800"),
- CreateHeaderField("cache-control", "no-cache"),
+ CreateHeaderField("accept-encoding", "gzip, deflate, br"), // 31
+ CreateHeaderField("accept-ranges", "bytes"), // 32
+ CreateHeaderField("access-control-allow-headers", "cache-control"), // 33
+ CreateHeaderField("access-control-allow-headers", "content-type"), // 34
+ CreateHeaderField("access-control-allow-origin", "*"), // 35
+ CreateHeaderField("cache-control", "max-age=0"), // 36
+ CreateHeaderField("cache-control", "max-age=2592000"), // 37
+ CreateHeaderField("cache-control", "max-age=604800"), // 38
+ CreateHeaderField("cache-control", "no-cache"), // 39
CreateHeaderField("cache-control", "no-store"), // 40
- CreateHeaderField("cache-control", "public, max-age=31536000"),
- CreateHeaderField("content-encoding", "br"),
- CreateHeaderField("content-encoding", "gzip"),
- CreateHeaderField("content-type", "application/dns-message"),
- CreateHeaderField("content-type", "application/javascript"),
- CreateHeaderField("content-type", "application/json"),
- CreateHeaderField("content-type", "application/x-www-form-urlencoded"),
- CreateHeaderField("content-type", "image/gif"),
- CreateHeaderField("content-type", "image/jpeg"),
+ CreateHeaderField("cache-control", "public, max-age=31536000"), // 41
+ CreateHeaderField("content-encoding", "br"), // 42
+ CreateHeaderField("content-encoding", "gzip"), // 43
+ CreateHeaderField("content-type", "application/dns-message"), // 44
+ CreateHeaderField("content-type", "application/javascript"), // 45
+ CreateHeaderField("content-type", "application/json"), // 46
+ CreateHeaderField("content-type", "application/x-www-form-urlencoded"), // 47
+ CreateHeaderField("content-type", "image/gif"), // 48
+ CreateHeaderField("content-type", "image/jpeg"), // 49
CreateHeaderField("content-type", "image/png"), // 50
- CreateHeaderField("content-type", "text/css"),
- CreateHeaderField("content-type", "text/html; charset=utf-8"),
- CreateHeaderField("content-type", "text/plain"),
- CreateHeaderField("content-type", "text/plain;charset=utf-8"),
- CreateHeaderField("range", "bytes=0-"),
- CreateHeaderField("strict-transport-security", "max-age=31536000"),
- CreateHeaderField("strict-transport-security", "max-age=31536000;includesubdomains"), // TODO confirm spaces here don't matter?
- CreateHeaderField("strict-transport-security", "max-age=31536000;includesubdomains; preload"),
- CreateHeaderField("vary", "accept-encoding"),
+ CreateHeaderField("content-type", "text/css"), // 51
+ CreateHeaderField("content-type", "text/html; charset=utf-8"), // 52
+ CreateHeaderField("content-type", "text/plain"), // 53
+ CreateHeaderField("content-type", "text/plain;charset=utf-8"), // 54
+ CreateHeaderField("range", "bytes=0-"), // 55
+ CreateHeaderField("strict-transport-security", "max-age=31536000"), // 56
+ CreateHeaderField("strict-transport-security", "max-age=31536000; includesubdomains"), // 57; TODO confirm spaces here don't matter?
+ CreateHeaderField("strict-transport-security", "max-age=31536000; includesubdomains; preload"), // 58
+ CreateHeaderField("vary", "accept-encoding"), // 59
CreateHeaderField("vary", "origin"), // 60
- CreateHeaderField("x-content-type-options", "nosniff"),
- CreateHeaderField("x-xss-protection", "1; mode=block"),
- CreateHeaderField(":status", "100"),
- CreateHeaderField(":status", "204"),
- CreateHeaderField(":status", "206"),
- CreateHeaderField(":status", "302"),
- CreateHeaderField(":status", "400"),
- CreateHeaderField(":status", "403"),
- CreateHeaderField(":status", "421"),
+ CreateHeaderField("x-content-type-options", "nosniff"), // 61
+ CreateHeaderField("x-xss-protection", "1; mode=block"), // 62
+ CreateHeaderField(":status", "100"), // 63
+ CreateHeaderField(":status", "204"), // 64
+ CreateHeaderField(":status", "206"), // 65
+ CreateHeaderField(":status", "302"), // 66
+ CreateHeaderField(":status", "400"), // 67
+ CreateHeaderField(":status", "403"), // 68
+ CreateHeaderField(":status", "421"), // 69
CreateHeaderField(":status", "425"), // 70
- CreateHeaderField(":status", "500"),
- CreateHeaderField("accept-language", ""),
- CreateHeaderField("access-control-allow-credentials", "FALSE"),
- CreateHeaderField("access-control-allow-credentials", "TRUE"),
- CreateHeaderField("access-control-allow-headers", "*"),
- CreateHeaderField("access-control-allow-methods", "get"),
- CreateHeaderField("access-control-allow-methods", "get, post, options"),
- CreateHeaderField("access-control-allow-methods", "options"),
- CreateHeaderField("access-control-expose-headers", "content-length"),
+ CreateHeaderField(":status", "500"), // 71
+ CreateHeaderField("accept-language", ""), // 72
+ CreateHeaderField("access-control-allow-credentials", "FALSE"), // 73
+ CreateHeaderField("access-control-allow-credentials", "TRUE"), // 74
+ CreateHeaderField("access-control-allow-headers", "*"), // 75
+ CreateHeaderField("access-control-allow-methods", "get"), // 76
+ CreateHeaderField("access-control-allow-methods", "get, post, options"), // 77
+ CreateHeaderField("access-control-allow-methods", "options"), // 78
+ CreateHeaderField("access-control-expose-headers", "content-length"), // 79
CreateHeaderField("access-control-request-headers", "content-type"), // 80
- CreateHeaderField("access-control-request-method", "get"),
- CreateHeaderField("access-control-request-method", "post"),
- CreateHeaderField("alt-svc", "clear"),
- CreateHeaderField("authorization", ""),
- CreateHeaderField("content-security-policy", "script-src 'none'; object-src 'none'; base-uri 'none'"),
- CreateHeaderField("early-data", "1"),
- CreateHeaderField("expect-ct", ""),
- CreateHeaderField("forwarded", ""),
- CreateHeaderField("if-range", ""),
+ CreateHeaderField("access-control-request-method", "get"), // 81
+ CreateHeaderField("access-control-request-method", "post"), // 82
+ CreateHeaderField("alt-svc", "clear"), // 83
+ CreateHeaderField("authorization", ""), // 84
+ CreateHeaderField("content-security-policy", "script-src 'none'; object-src 'none'; base-uri 'none'"), // 85
+ CreateHeaderField("early-data", "1"), // 86
+ CreateHeaderField("expect-ct", ""), // 87
+ CreateHeaderField("forwarded", ""), // 88
+ CreateHeaderField("if-range", ""), // 89
CreateHeaderField("origin", ""), // 90
- CreateHeaderField("purpose", "prefetch"),
- CreateHeaderField("server", ""),
- CreateHeaderField("timing-allow-origin", "*"),
- CreateHeaderField("upgrading-insecure-requests", "1"),
- CreateHeaderField("user-agent", ""),
- CreateHeaderField("x-forwarded-for", ""),
- CreateHeaderField("x-frame-options", "deny"),
- CreateHeaderField("x-frame-options", "sameorigin"),
+ CreateHeaderField("purpose", "prefetch"), // 91
+ CreateHeaderField("server", ""), // 92
+ CreateHeaderField("timing-allow-origin", "*"), // 93
+ CreateHeaderField("upgrading-insecure-requests", "1"), // 94
+ CreateHeaderField("user-agent", ""), // 95
+ CreateHeaderField("x-forwarded-for", ""), // 96
+ CreateHeaderField("x-frame-options", "deny"), // 97
+ CreateHeaderField("x-frame-options", "sameorigin"), // 98
};
private static HeaderField CreateHeaderField(string name, string value)
await SendFrameAsync(SettingsFrame, buffer.AsMemory(0, bytesWritten)).ConfigureAwait(false);
}
- private Memory<byte> ConstructHeadersPayload(IEnumerable<HttpHeaderData> headers)
+ private Memory<byte> ConstructHeadersPayload(HttpStatusCode statusCode, IEnumerable<HttpHeaderData> headers, bool qpackEncodeStatus = false)
{
int bufferLength = QPackTestEncoder.MaxPrefixLength;
+ if (qpackEncodeStatus)
+ {
+ bufferLength += QPackTestEncoder.MaxVarIntLength * 2 + ":status".Length + 3;
+ }
+ else
+ {
+ headers = headers.Prepend(new HttpHeaderData(":status", ((int)statusCode).ToString(CultureInfo.InvariantCulture)));
+ };
+
foreach (HttpHeaderData header in headers)
{
Debug.Assert(header.Name != null);
bytesWritten += QPackTestEncoder.EncodePrefix(buffer.AsSpan(bytesWritten), 0, 0);
+ if (qpackEncodeStatus)
+ {
+ bytesWritten += QPackTestEncoder.EncodeStatusCode((int)statusCode, buffer.AsSpan(bytesWritten));
+ }
+
foreach (HttpHeaderData header in headers)
{
bytesWritten += QPackTestEncoder.EncodeHeader(buffer.AsSpan(bytesWritten), header.Name, header.Value, header.ValueEncoding, header.HuffmanEncoded ? QPackFlags.HuffmanEncode : QPackFlags.None);
return buffer.AsMemory(0, bytesWritten);
}
- private async Task SendHeadersFrameAsync(IEnumerable<HttpHeaderData> headers)
+ private async Task SendHeadersFrameAsync(HttpStatusCode statusCode, IEnumerable<HttpHeaderData> headers, bool qpackEncodeStatus = false)
{
- await SendFrameAsync(HeadersFrame, ConstructHeadersPayload(headers)).ConfigureAwait(false);
+ await SendFrameAsync(HeadersFrame, ConstructHeadersPayload(statusCode, headers, qpackEncodeStatus)).ConfigureAwait(false);
}
- private async Task SendPartialHeadersFrameAsync(IEnumerable<HttpHeaderData> headers)
+ private async Task SendPartialHeadersFrameAsync(HttpStatusCode statusCode, IEnumerable<HttpHeaderData> headers)
{
- Memory<byte> payload = ConstructHeadersPayload(headers);
+ Memory<byte> payload = ConstructHeadersPayload(statusCode, headers);
await SendFrameHeaderAsync(HeadersFrame, payload.Length);
await SendResponseBodyAsync(Encoding.UTF8.GetBytes(content ?? ""), isFinal).ConfigureAwait(false);
}
- private IEnumerable<HttpHeaderData> PrepareHeaders(HttpStatusCode statusCode, IEnumerable<HttpHeaderData> headers)
+ private IEnumerable<HttpHeaderData> PrepareHeaders(IEnumerable<HttpHeaderData> headers)
{
headers ??= Enumerable.Empty<HttpHeaderData>();
// Some tests use Content-Length with a null value to indicate Content-Length should not be set.
headers = headers.Where(x => x.Name != "Content-Length" || x.Value != null);
- headers = headers.Prepend(new HttpHeaderData(":status", ((int)statusCode).ToString(CultureInfo.InvariantCulture)));
-
return headers;
}
public async Task SendResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IEnumerable<HttpHeaderData> headers = null)
{
- headers = PrepareHeaders(statusCode, headers);
- await SendHeadersFrameAsync(headers).ConfigureAwait(false);
+ headers = PrepareHeaders(headers);
+ await SendHeadersFrameAsync(statusCode, headers).ConfigureAwait(false);
+ }
+
+ public async Task SendResponseHeadersWithEncodedStatusAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IEnumerable<HttpHeaderData> headers = null)
+ {
+ headers = PrepareHeaders(headers);
+ await SendHeadersFrameAsync(statusCode, headers, qpackEncodeStatus: true).ConfigureAwait(false);
}
public async Task SendPartialResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IEnumerable<HttpHeaderData> headers = null)
{
- headers = PrepareHeaders(statusCode, headers);
- await SendPartialHeadersFrameAsync(headers).ConfigureAwait(false);
+ headers = PrepareHeaders(headers);
+ await SendPartialHeadersFrameAsync(statusCode, headers).ConfigureAwait(false);
}
public async Task SendResponseBodyAsync(byte[] content, bool isFinal = true)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Collections.Generic;
using System.Diagnostics;
+using System.Globalization;
using System.Text;
namespace System.Net.Test.Common
{
return HPackEncoder.EncodeInteger(value, prefix, prefixMask, buffer);
}
+
+ // from System.Net.Http.QPack.H3StaticTable
+ private static readonly Dictionary<int, int> s_statusIndex = new Dictionary<int, int>
+ {
+ [103] = 24,
+ [200] = 25,
+ [304] = 26,
+ [404] = 27,
+ [503] = 28,
+ [100] = 63,
+ [204] = 64,
+ [206] = 65,
+ [302] = 66,
+ [400] = 67,
+ [403] = 68,
+ [421] = 69,
+ [425] = 70,
+ [500] = 71,
+ };
+
+ public static int EncodeStatusCode(int statusCode, Span<byte> buffer)
+ {
+ if (s_statusIndex.TryGetValue(statusCode, out var statusIdx))
+ {
+ // Indexed Header Field
+ return EncodeHeader(buffer, statusIdx);
+ }
+ else
+ {
+ // Literal Header Field With Name Reference -- Index of any status present in the table can be used for reference
+ return EncodeHeader(buffer, s_statusIndex[100], statusCode.ToString(CultureInfo.InvariantCulture), valueEncoding: null);
+ }
+ }
}
[Flags]
// TODO: can we put some of this logic into H3StaticTable and/or generate it using data that is already there?
internal static (HeaderDescriptor descriptor, string value)[] HeaderLookup { get; } = new (HeaderDescriptor descriptor, string value)[]
{
- (new HeaderDescriptor(":authority"), ""),
- (new HeaderDescriptor(":path"), "/"),
- (new HeaderDescriptor(KnownHeaders.Age), "0"),
- (new HeaderDescriptor(KnownHeaders.ContentDisposition), ""),
- (new HeaderDescriptor(KnownHeaders.ContentLength), "0"),
- (new HeaderDescriptor(KnownHeaders.Date), ""),
- (new HeaderDescriptor(KnownHeaders.ETag), ""),
- (new HeaderDescriptor(KnownHeaders.IfModifiedSince), ""),
- (new HeaderDescriptor(KnownHeaders.IfNoneMatch), ""),
- (new HeaderDescriptor(KnownHeaders.LastModified), ""),
- (new HeaderDescriptor(KnownHeaders.Link), ""),
- (new HeaderDescriptor(KnownHeaders.Location), ""),
- (new HeaderDescriptor(KnownHeaders.Referer), ""),
- (new HeaderDescriptor(KnownHeaders.SetCookie), ""),
- (new HeaderDescriptor(":method"), "CONNECT"),
- (new HeaderDescriptor(":method"), "DELETE"),
- (new HeaderDescriptor(":method"), "GET"),
- (new HeaderDescriptor(":method"), "HEAD"),
- (new HeaderDescriptor(":method"), "OPTIONS"),
- (new HeaderDescriptor(":method"), "POST"),
- (new HeaderDescriptor(":method"), "PUT"),
- (new HeaderDescriptor(":scheme"), "http"),
- (new HeaderDescriptor(":scheme"), "https"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "103"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "200"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "304"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "404"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "503"),
- (new HeaderDescriptor(KnownHeaders.Accept), "*/*"),
- (new HeaderDescriptor(KnownHeaders.Accept), "application/dns-message"),
- (new HeaderDescriptor(KnownHeaders.AcceptEncoding), "gzip, deflate, br"),
- (new HeaderDescriptor(KnownHeaders.AcceptRanges), "bytes"),
- (new HeaderDescriptor(KnownHeaders.AccessControlAllowHeaders), "cache-control"),
- (new HeaderDescriptor(KnownHeaders.AccessControlAllowHeaders), "content-type"),
- (new HeaderDescriptor(KnownHeaders.AccessControlAllowHeaders), "*"),
- (new HeaderDescriptor(KnownHeaders.AccessControlAllowOrigin), "*"),
- (new HeaderDescriptor(KnownHeaders.CacheControl), "max-age=0"),
- (new HeaderDescriptor(KnownHeaders.CacheControl), "max-age=2592000"),
- (new HeaderDescriptor(KnownHeaders.CacheControl), "max-age=604800"),
- (new HeaderDescriptor(KnownHeaders.CacheControl), "no-cache"),
- (new HeaderDescriptor(KnownHeaders.CacheControl), "no-store"),
- (new HeaderDescriptor(KnownHeaders.CacheControl), "public, max-age=31536000"),
- (new HeaderDescriptor(KnownHeaders.ContentEncoding), "br"),
- (new HeaderDescriptor(KnownHeaders.ContentEncoding), "gzip"),
- (new HeaderDescriptor(KnownHeaders.ContentType), "application/dns-message"),
- (new HeaderDescriptor(KnownHeaders.ContentType), "application/javascript"),
- (new HeaderDescriptor(KnownHeaders.ContentType), "application/json"),
- (new HeaderDescriptor(KnownHeaders.ContentType), "application/x-www-form-urlencoded"),
- (new HeaderDescriptor(KnownHeaders.ContentType), "image/gif"),
- (new HeaderDescriptor(KnownHeaders.ContentType), "image/jpeg"),
- (new HeaderDescriptor(KnownHeaders.ContentType), "image/png"),
- (new HeaderDescriptor(KnownHeaders.ContentType), "text/css"),
- (new HeaderDescriptor(KnownHeaders.ContentType), "text/html; charset=utf-8"), // Whitespace is correct, see spec.
- (new HeaderDescriptor(KnownHeaders.ContentType), "text/plain"),
- (new HeaderDescriptor(KnownHeaders.ContentType), "text/plain;charset=utf-8"), // Whitespace is correct, see spec.
- (new HeaderDescriptor(KnownHeaders.Range), "bytes=0-"),
- (new HeaderDescriptor(KnownHeaders.StrictTransportSecurity), "max-age=31536000"),
- (new HeaderDescriptor(KnownHeaders.StrictTransportSecurity), "max-age=31536000; includesubdomains"),
- (new HeaderDescriptor(KnownHeaders.StrictTransportSecurity), "max-age=31536000; includesubdomains; preload"),
- (new HeaderDescriptor(KnownHeaders.Vary), "accept-encoding"),
- (new HeaderDescriptor(KnownHeaders.Vary), "origin"),
- (new HeaderDescriptor(KnownHeaders.XContentTypeOptions), "nosniff"),
- (new HeaderDescriptor("x-xss-protection"), "1; mode=block"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "100"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "204"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "206"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "302"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "400"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "403"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "421"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "425"),
- (new HeaderDescriptor(KnownHeaders.PseudoStatus), "500"),
- (new HeaderDescriptor(KnownHeaders.AcceptLanguage), ""),
- (new HeaderDescriptor(KnownHeaders.AccessControlAllowCredentials), "FALSE"),
- (new HeaderDescriptor(KnownHeaders.AccessControlAllowCredentials), "TRUE"),
- (new HeaderDescriptor(KnownHeaders.AccessControlAllowHeaders), "*"),
- (new HeaderDescriptor(KnownHeaders.AccessControlAllowMethods), "get"),
- (new HeaderDescriptor(KnownHeaders.AccessControlAllowMethods), "get, post, options"),
- (new HeaderDescriptor(KnownHeaders.AccessControlAllowMethods), "options"),
- (new HeaderDescriptor(KnownHeaders.AccessControlExposeHeaders), "content-length"),
- (new HeaderDescriptor("access-control-request-headers"), "content-type"),
- (new HeaderDescriptor("access-control-request-method"), "get"),
- (new HeaderDescriptor("access-control-request-method"), "post"),
- (new HeaderDescriptor(KnownHeaders.AltSvc), "clear"),
- (new HeaderDescriptor(KnownHeaders.Authorization), ""),
- (new HeaderDescriptor(KnownHeaders.ContentSecurityPolicy), "script-src 'none'; object-src 'none'; base-uri 'none'"),
- (new HeaderDescriptor("early-data"), "1"),
- (new HeaderDescriptor("expect-ct"), ""),
- (new HeaderDescriptor("forwarded"), ""),
- (new HeaderDescriptor(KnownHeaders.IfRange), ""),
- (new HeaderDescriptor(KnownHeaders.Origin), ""),
- (new HeaderDescriptor("purpose"), "prefetch"),
- (new HeaderDescriptor(KnownHeaders.Server), ""),
- (new HeaderDescriptor("timing-allow-origin"), "*"),
- (new HeaderDescriptor(KnownHeaders.UpgradeInsecureRequests), "1"),
- (new HeaderDescriptor(KnownHeaders.UserAgent), ""),
- (new HeaderDescriptor("x-forwarded-for"), ""),
- (new HeaderDescriptor(KnownHeaders.XFrameOptions), "deny"),
- (new HeaderDescriptor(KnownHeaders.XFrameOptions), "sameorigin")
+ (new HeaderDescriptor(":authority"), ""), // 0
+ (new HeaderDescriptor(":path"), "/"), // 1
+ (new HeaderDescriptor(KnownHeaders.Age), "0"), // 2
+ (new HeaderDescriptor(KnownHeaders.ContentDisposition), ""), // 3
+ (new HeaderDescriptor(KnownHeaders.ContentLength), "0"), // 4
+ (new HeaderDescriptor(KnownHeaders.Cookie), ""), // 5
+ (new HeaderDescriptor(KnownHeaders.Date), ""), // 6
+ (new HeaderDescriptor(KnownHeaders.ETag), ""), // 7
+ (new HeaderDescriptor(KnownHeaders.IfModifiedSince), ""), // 8
+ (new HeaderDescriptor(KnownHeaders.IfNoneMatch), ""), // 9
+ (new HeaderDescriptor(KnownHeaders.LastModified), ""), // 10
+ (new HeaderDescriptor(KnownHeaders.Link), ""), // 11
+ (new HeaderDescriptor(KnownHeaders.Location), ""), // 12
+ (new HeaderDescriptor(KnownHeaders.Referer), ""), // 13
+ (new HeaderDescriptor(KnownHeaders.SetCookie), ""), // 14
+ (new HeaderDescriptor(":method"), "CONNECT"), // 15
+ (new HeaderDescriptor(":method"), "DELETE"), // 16
+ (new HeaderDescriptor(":method"), "GET"), // 17
+ (new HeaderDescriptor(":method"), "HEAD"), // 18
+ (new HeaderDescriptor(":method"), "OPTIONS"), // 19
+ (new HeaderDescriptor(":method"), "POST"), // 20
+ (new HeaderDescriptor(":method"), "PUT"), // 21
+ (new HeaderDescriptor(":scheme"), "http"), // 22
+ (new HeaderDescriptor(":scheme"), "https"), // 23
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "103"), // 24
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "200"), // 25
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "304"), // 26
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "404"), // 27
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "503"), // 28
+ (new HeaderDescriptor(KnownHeaders.Accept), "*/*"), // 29
+ (new HeaderDescriptor(KnownHeaders.Accept), "application/dns-message"), // 30
+ (new HeaderDescriptor(KnownHeaders.AcceptEncoding), "gzip, deflate, br"), // 31
+ (new HeaderDescriptor(KnownHeaders.AcceptRanges), "bytes"), // 32
+ (new HeaderDescriptor(KnownHeaders.AccessControlAllowHeaders), "cache-control"), // 33
+ (new HeaderDescriptor(KnownHeaders.AccessControlAllowHeaders), "content-type"), // 34
+ (new HeaderDescriptor(KnownHeaders.AccessControlAllowOrigin), "*"), // 35
+ (new HeaderDescriptor(KnownHeaders.CacheControl), "max-age=0"), // 36
+ (new HeaderDescriptor(KnownHeaders.CacheControl), "max-age=2592000"), // 37
+ (new HeaderDescriptor(KnownHeaders.CacheControl), "max-age=604800"), // 38
+ (new HeaderDescriptor(KnownHeaders.CacheControl), "no-cache"), // 39
+ (new HeaderDescriptor(KnownHeaders.CacheControl), "no-store"), // 40
+ (new HeaderDescriptor(KnownHeaders.CacheControl), "public, max-age=31536000"), // 41
+ (new HeaderDescriptor(KnownHeaders.ContentEncoding), "br"), // 42
+ (new HeaderDescriptor(KnownHeaders.ContentEncoding), "gzip"), // 43
+ (new HeaderDescriptor(KnownHeaders.ContentType), "application/dns-message"), // 44
+ (new HeaderDescriptor(KnownHeaders.ContentType), "application/javascript"), // 45
+ (new HeaderDescriptor(KnownHeaders.ContentType), "application/json"), // 46
+ (new HeaderDescriptor(KnownHeaders.ContentType), "application/x-www-form-urlencoded"), // 47
+ (new HeaderDescriptor(KnownHeaders.ContentType), "image/gif"), // 48
+ (new HeaderDescriptor(KnownHeaders.ContentType), "image/jpeg"), // 49
+ (new HeaderDescriptor(KnownHeaders.ContentType), "image/png"), // 50
+ (new HeaderDescriptor(KnownHeaders.ContentType), "text/css"), // 51
+ (new HeaderDescriptor(KnownHeaders.ContentType), "text/html; charset=utf-8"), // 52; Whitespace is correct, see spec.
+ (new HeaderDescriptor(KnownHeaders.ContentType), "text/plain"), // 53
+ (new HeaderDescriptor(KnownHeaders.ContentType), "text/plain;charset=utf-8"), // 54; Whitespace is correct, see spec.
+ (new HeaderDescriptor(KnownHeaders.Range), "bytes=0-"), // 55
+ (new HeaderDescriptor(KnownHeaders.StrictTransportSecurity), "max-age=31536000"), // 56
+ (new HeaderDescriptor(KnownHeaders.StrictTransportSecurity), "max-age=31536000; includesubdomains"), // 57
+ (new HeaderDescriptor(KnownHeaders.StrictTransportSecurity), "max-age=31536000; includesubdomains; preload"), // 58
+ (new HeaderDescriptor(KnownHeaders.Vary), "accept-encoding"), // 59
+ (new HeaderDescriptor(KnownHeaders.Vary), "origin"), // 60
+ (new HeaderDescriptor(KnownHeaders.XContentTypeOptions), "nosniff"), // 61
+ (new HeaderDescriptor("x-xss-protection"), "1; mode=block"), // 62
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "100"), // 63
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "204"), // 64
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "206"), // 65
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "302"), // 66
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "400"), // 67
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "403"), // 68
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "421"), // 69
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "425"), // 70
+ (new HeaderDescriptor(KnownHeaders.PseudoStatus), "500"), // 71
+ (new HeaderDescriptor(KnownHeaders.AcceptLanguage), ""), // 72
+ (new HeaderDescriptor(KnownHeaders.AccessControlAllowCredentials), "FALSE"), // 73
+ (new HeaderDescriptor(KnownHeaders.AccessControlAllowCredentials), "TRUE"), // 74
+ (new HeaderDescriptor(KnownHeaders.AccessControlAllowHeaders), "*"), // 75
+ (new HeaderDescriptor(KnownHeaders.AccessControlAllowMethods), "get"), // 76
+ (new HeaderDescriptor(KnownHeaders.AccessControlAllowMethods), "get, post, options"), // 77
+ (new HeaderDescriptor(KnownHeaders.AccessControlAllowMethods), "options"), // 78
+ (new HeaderDescriptor(KnownHeaders.AccessControlExposeHeaders), "content-length"), // 79
+ (new HeaderDescriptor("access-control-request-headers"), "content-type"), // 80
+ (new HeaderDescriptor("access-control-request-method"), "get"), // 81
+ (new HeaderDescriptor("access-control-request-method"), "post"), // 82
+ (new HeaderDescriptor(KnownHeaders.AltSvc), "clear"), // 83
+ (new HeaderDescriptor(KnownHeaders.Authorization), ""), // 84
+ (new HeaderDescriptor(KnownHeaders.ContentSecurityPolicy), "script-src 'none'; object-src 'none'; base-uri 'none'"), // 85
+ (new HeaderDescriptor("early-data"), "1"), // 86
+ (new HeaderDescriptor("expect-ct"), ""), // 87
+ (new HeaderDescriptor("forwarded"), ""), // 88
+ (new HeaderDescriptor(KnownHeaders.IfRange), ""), // 89
+ (new HeaderDescriptor(KnownHeaders.Origin), ""), // 90
+ (new HeaderDescriptor("purpose"), "prefetch"), // 91
+ (new HeaderDescriptor(KnownHeaders.Server), ""), // 92
+ (new HeaderDescriptor("timing-allow-origin"), "*"), // 93
+ (new HeaderDescriptor(KnownHeaders.UpgradeInsecureRequests), "1"), // 94
+ (new HeaderDescriptor(KnownHeaders.UserAgent), ""), // 95
+ (new HeaderDescriptor("x-forwarded-for"), ""), // 96
+ (new HeaderDescriptor(KnownHeaders.XFrameOptions), "deny"), // 97
+ (new HeaderDescriptor(KnownHeaders.XFrameOptions), "sameorigin") // 98
};
}
}
throw new Http3ConnectionException(Http3ErrorCode.ProtocolError);
}
- int statusCode = staticIndex switch
+ int statusCode;
+ if (staticValue != null) // Indexed Header Field -- both name and value are taken from the table
{
- H3StaticTable.Status103 => 103,
- H3StaticTable.Status200 => 200,
- H3StaticTable.Status304 => 304,
- H3StaticTable.Status404 => 404,
- H3StaticTable.Status503 => 503,
- H3StaticTable.Status100 => 100,
- H3StaticTable.Status204 => 204,
- H3StaticTable.Status206 => 206,
- H3StaticTable.Status302 => 302,
- H3StaticTable.Status400 => 400,
- H3StaticTable.Status403 => 403,
- H3StaticTable.Status421 => 421,
- H3StaticTable.Status425 => 425,
- H3StaticTable.Status500 => 500,
- _ => HttpConnectionBase.ParseStatusCode(literalValue),
- };
+ statusCode = staticIndex switch
+ {
+ H3StaticTable.Status103 => 103,
+ H3StaticTable.Status200 => 200,
+ H3StaticTable.Status304 => 304,
+ H3StaticTable.Status404 => 404,
+ H3StaticTable.Status503 => 503,
+ H3StaticTable.Status100 => 100,
+ H3StaticTable.Status204 => 204,
+ H3StaticTable.Status206 => 206,
+ H3StaticTable.Status302 => 302,
+ H3StaticTable.Status400 => 400,
+ H3StaticTable.Status403 => 403,
+ H3StaticTable.Status421 => 421,
+ H3StaticTable.Status425 => 425,
+ H3StaticTable.Status500 => 500,
+ // We should never get here, at least while we only use static table. But we can still parse staticValue.
+ _ => ParseStatusCode(staticIndex, staticValue)
+ };
+
+ int ParseStatusCode(int? index, string value)
+ {
+ string message = $"Unexpected QPACK table reference for Status code: index={index} value=\'{value}\'";
+ Debug.Fail(message);
+ if (NetEventSource.Log.IsEnabled()) Trace(message);
+
+ // TODO: The parsing is not optimal, but I don't expect this line to be executed at all for now.
+ return HttpConnectionBase.ParseStatusCode(Encoding.ASCII.GetBytes(value));
+ }
+ }
+ else // Literal Header Field With Name Reference -- only name is taken from the table
+ {
+ statusCode = HttpConnectionBase.ParseStatusCode(literalValue);
+ }
_response = new HttpResponseMessage()
{
return (SslApplicationProtocol)alpn;
}
+ [ConditionalTheory(nameof(IsMsQuicSupported))]
+ [MemberData(nameof(StatusCodesTestData))]
+ public async Task StatusCodes_ReceiveSuccess(HttpStatusCode statusCode, bool qpackEncode)
+ {
+ using Http3LoopbackServer server = CreateHttp3LoopbackServer();
+
+ Http3LoopbackConnection connection = null;
+ Task serverTask = Task.Run(async () =>
+ {
+ connection = (Http3LoopbackConnection)await server.EstablishGenericConnectionAsync();
+ using Http3LoopbackStream stream = await connection.AcceptRequestStreamAsync();
+
+ HttpRequestData request = await stream.ReadRequestDataAsync().ConfigureAwait(false);
+
+ if (qpackEncode)
+ {
+ await stream.SendResponseHeadersWithEncodedStatusAsync(statusCode).ConfigureAwait(false);
+ }
+ else
+ {
+ await stream.SendResponseHeadersAsync(statusCode).ConfigureAwait(false);
+ }
+ });
+
+ using HttpClient client = CreateHttpClient();
+ using HttpRequestMessage request = new()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = server.Address,
+ Version = HttpVersion30,
+ VersionPolicy = HttpVersionPolicy.RequestVersionExact
+ };
+ HttpResponseMessage response = await client.SendAsync(request).WaitAsync(TimeSpan.FromSeconds(10));
+
+ Assert.Equal(statusCode, response.StatusCode);
+
+ await serverTask;
+ Assert.NotNull(connection);
+ connection.Dispose();
+ }
+
+ public static TheoryData<HttpStatusCode, bool> StatusCodesTestData()
+ {
+ var statuses = Enum.GetValues(typeof(HttpStatusCode)).Cast<HttpStatusCode>().Where(s => s >= HttpStatusCode.OK); // exclude informational
+ var data = new TheoryData<HttpStatusCode, bool>();
+ foreach (var status in statuses)
+ {
+ data.Add(status, true);
+ data.Add(status, false);
+ }
+ return data;
+ }
+
/// <summary>
/// These are public interop test servers for various QUIC and HTTP/3 implementations,
/// taken from https://github.com/quicwg/base-drafts/wiki/Implementations and https://bagder.github.io/HTTP3-test/.