Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,102 +50,102 @@ internal static partial class H3StaticTable
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)
Expand Down
42 changes: 30 additions & 12 deletions src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,19 @@ public async Task SendSettingsFrameAsync(ICollection<(long settingId, long setti
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);
Expand All @@ -96,6 +105,11 @@ private Memory<byte> ConstructHeadersPayload(IEnumerable<HttpHeaderData> headers

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);
Expand All @@ -104,14 +118,14 @@ private Memory<byte> ConstructHeadersPayload(IEnumerable<HttpHeaderData> headers
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);

Expand Down Expand Up @@ -234,28 +248,32 @@ public async Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.O
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)
Expand Down
35 changes: 35 additions & 0 deletions src/libraries/Common/tests/System/Net/Http/QPackTestEncoder.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// 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
Expand Down Expand Up @@ -130,6 +132,39 @@ public static int EncodeInteger(Span<byte> buffer, int value, byte prefix, byte
{
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]
Expand Down
Loading