|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.IISIntegration.FunctionalTests;
using Microsoft.AspNetCore.Server.IIS;
using Microsoft.AspNetCore.Server.IISIntegration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Xunit;
namespace TestSite;
public partial class Startup
{
public static bool StartupHookCalled;
private IHttpContextAccessor _httpContextAccessor;
public void Configure(IApplicationBuilder app, IHttpContextAccessor httpContextAccessor)
{
if (Environment.GetEnvironmentVariable("ENABLE_HTTPS_REDIRECTION") != null)
{
app.UseHttpsRedirection();
}
#if !FORWARDCOMPAT
app.UseWebSockets();
#endif
TestStartup.Register(app, this);
_httpContextAccessor = httpContextAccessor;
}
public void ConfigureServices(IServiceCollection serviceCollection)
{
serviceCollection.AddResponseCompression();
serviceCollection.AddHttpContextAccessor();
}
#if FORWARDCOMPAT
private async Task ContentRootPath(HttpContext ctx) => await ctx.Response.WriteAsync(ctx.RequestServices.GetService<Microsoft.AspNetCore.Hosting.IHostingEnvironment>().ContentRootPath);
private async Task WebRootPath(HttpContext ctx) => await ctx.Response.WriteAsync(ctx.RequestServices.GetService<Microsoft.AspNetCore.Hosting.IHostingEnvironment>().WebRootPath);
#else
private async Task ContentRootPath(HttpContext ctx) => await ctx.Response.WriteAsync(ctx.RequestServices.GetService<IWebHostEnvironment>().ContentRootPath);
private async Task WebRootPath(HttpContext ctx) => await ctx.Response.WriteAsync(ctx.RequestServices.GetService<IWebHostEnvironment>().WebRootPath);
#endif
private async Task CurrentDirectory(HttpContext ctx) => await ctx.Response.WriteAsync(Environment.CurrentDirectory);
private async Task BaseDirectory(HttpContext ctx) => await ctx.Response.WriteAsync(AppContext.BaseDirectory);
private async Task IIISEnvironmentFeatureConfig(HttpContext ctx)
{
var config = ctx.RequestServices.GetService<IConfiguration>();
await ctx.Response.WriteAsync("IIS Version: " + config["IIS_VERSION"] + Environment.NewLine);
await ctx.Response.WriteAsync("ApplicationId: " + config["IIS_APPLICATION_ID"] + Environment.NewLine);
await ctx.Response.WriteAsync("Application Path: " + config["IIS_PHYSICAL_PATH"] + Environment.NewLine);
await ctx.Response.WriteAsync("Application Virtual Path: " + config["IIS_APPLICATION_VIRTUAL_PATH"] + Environment.NewLine);
await ctx.Response.WriteAsync("Application Config Path: " + config["IIS_APP_CONFIG_PATH"] + Environment.NewLine);
await ctx.Response.WriteAsync("AppPool ID: " + config["IIS_APP_POOL_ID"] + Environment.NewLine);
await ctx.Response.WriteAsync("AppPool Config File: " + config["IIS_APP_POOL_CONFIG_FILE"] + Environment.NewLine);
await ctx.Response.WriteAsync("Site ID: " + config["IIS_SITE_ID"] + Environment.NewLine);
await ctx.Response.WriteAsync("Site Name: " + config["IIS_SITE_NAME"]);
}
#if !FORWARDCOMPAT
private async Task IIISEnvironmentFeature(HttpContext ctx)
{
var envFeature = ctx.RequestServices.GetService<IServer>().Features.Get<IIISEnvironmentFeature>();
await ctx.Response.WriteAsync("IIS Version: " + envFeature.IISVersion + Environment.NewLine);
await ctx.Response.WriteAsync("ApplicationId: " + envFeature.ApplicationId + Environment.NewLine);
await ctx.Response.WriteAsync("Application Path: " + envFeature.ApplicationPhysicalPath + Environment.NewLine);
await ctx.Response.WriteAsync("Application Virtual Path: " + envFeature.ApplicationVirtualPath + Environment.NewLine);
await ctx.Response.WriteAsync("Application Config Path: " + envFeature.AppConfigPath + Environment.NewLine);
await ctx.Response.WriteAsync("AppPool ID: " + envFeature.AppPoolId + Environment.NewLine);
await ctx.Response.WriteAsync("AppPool Config File: " + envFeature.AppPoolConfigFile + Environment.NewLine);
await ctx.Response.WriteAsync("Site ID: " + envFeature.SiteId + Environment.NewLine);
await ctx.Response.WriteAsync("Site Name: " + envFeature.SiteName);
}
#endif
private async Task ASPNETCORE_IIS_PHYSICAL_PATH(HttpContext ctx) => await ctx.Response.WriteAsync(Environment.GetEnvironmentVariable("ASPNETCORE_IIS_PHYSICAL_PATH"));
private async Task ServerAddresses(HttpContext ctx)
{
var serverAddresses = ctx.RequestServices.GetService<IServer>().Features.Get<IServerAddressesFeature>();
await ctx.Response.WriteAsync(string.Join(",", serverAddresses.Addresses));
}
private async Task CheckProtocol(HttpContext ctx)
{
await ctx.Response.WriteAsync(ctx.Request.Protocol);
}
private async Task ConsoleWrite(HttpContext ctx)
{
Console.WriteLine("TEST MESSAGE");
await ctx.Response.WriteAsync("Hello World");
}
private async Task ConsoleErrorWrite(HttpContext ctx)
{
Console.Error.WriteLine("TEST MESSAGE");
await ctx.Response.WriteAsync("Hello World");
}
public async Task Auth(HttpContext ctx)
{
var authProvider = ctx.RequestServices.GetService<IAuthenticationSchemeProvider>();
var authScheme = (await authProvider.GetAllSchemesAsync()).SingleOrDefault();
await ctx.Response.WriteAsync(authScheme?.Name ?? "null");
if (ctx.User.Identity.Name != null)
{
await ctx.Response.WriteAsync(":" + ctx.User.Identity.Name);
}
}
public async Task GetClientCert(HttpContext context)
{
var clientCert = context.Connection.ClientCertificate;
await context.Response.WriteAsync(clientCert != null ? $"Enabled;{clientCert.GetCertHashString()}" : "Disabled");
}
private static int _waitingRequestCount;
public Task WaitForAbort(HttpContext context)
{
Interlocked.Increment(ref _waitingRequestCount);
try
{
context.RequestAborted.WaitHandle.WaitOne();
return Task.CompletedTask;
}
finally
{
Interlocked.Decrement(ref _waitingRequestCount);
}
}
public Task Abort(HttpContext context)
{
context.Abort();
return Task.CompletedTask;
}
public async Task WaitingRequestCount(HttpContext context)
{
await context.Response.WriteAsync(_waitingRequestCount.ToString(CultureInfo.InvariantCulture));
}
public Task CreateFile(HttpContext context)
{
#if FORWARDCOMPAT
var hostingEnv = context.RequestServices.GetService<Microsoft.AspNetCore.Hosting.IHostingEnvironment>();
#else
var hostingEnv = context.RequestServices.GetService<IWebHostEnvironment>();
#endif
if (context.Connection.LocalIpAddress == null || context.Connection.RemoteIpAddress == null)
{
throw new Exception("Failed to set local and remote ip addresses");
}
File.WriteAllText(System.IO.Path.Combine(hostingEnv.ContentRootPath, "Started.txt"), "");
return Task.CompletedTask;
}
public Task ConnectionClose(HttpContext context)
{
context.Response.Headers["connection"] = "close";
return Task.CompletedTask;
}
public Task OverrideServer(HttpContext context)
{
context.Response.Headers["Server"] = "MyServer/7.8";
return Task.CompletedTask;
}
public void CompressedData(IApplicationBuilder builder)
{
builder.UseResponseCompression();
// write random bytes to check that compressed data is passed through
builder.Run(
async context =>
{
context.Response.ContentType = "text/html";
await context.Response.Body.WriteAsync(new byte[100], 0, 100);
});
}
private async Task GetEnvironmentVariable(HttpContext ctx)
{
await ctx.Response.WriteAsync(Environment.GetEnvironmentVariable(ctx.Request.Query["name"].ToString()));
}
private async Task ServerVariable(HttpContext ctx)
{
var varName = ctx.Request.Query["q"];
var newValue = ctx.Request.Query["v"];
var feature = ctx.Features.Get<IServerVariablesFeature>();
if (newValue.Count != 0)
{
feature[varName] = newValue;
}
await ctx.Response.WriteAsync($"{varName}: {feature[varName] ?? "(null)"}");
}
private async Task AuthenticationAnonymous(HttpContext ctx)
{
await ctx.Response.WriteAsync("Anonymous?" + !ctx.User.Identity.IsAuthenticated);
}
private async Task AuthenticationRestricted(HttpContext ctx)
{
if (ctx.User.Identity.IsAuthenticated)
{
await ctx.Response.WriteAsync(ctx.User.Identity.AuthenticationType);
}
else
{
await ctx.ChallengeAsync(IISServerDefaults.AuthenticationScheme);
}
}
private async Task AuthenticationForbidden(HttpContext ctx)
{
await ctx.ForbidAsync(IISServerDefaults.AuthenticationScheme);
}
private async Task AuthenticationRestrictedNTLM(HttpContext ctx)
{
if (string.Equals("NTLM", ctx.User.Identity.AuthenticationType, StringComparison.Ordinal))
{
await ctx.Response.WriteAsync("NTLM");
}
else
{
await ctx.ChallengeAsync(IISServerDefaults.AuthenticationScheme);
}
}
private Task PathAndPathBase(HttpContext ctx)
{
return ctx.Response.WriteAsync($"PathBase: {ctx.Request.PathBase.Value}; Path: {ctx.Request.Path.Value}");
}
private async Task FeatureCollectionSetRequestFeatures(HttpContext ctx)
{
try
{
Assert.Equal("GET", ctx.Request.Method);
ctx.Request.Method = "test";
Assert.Equal("test", ctx.Request.Method);
Assert.Equal("http", ctx.Request.Scheme);
ctx.Request.Scheme = "test";
Assert.Equal("test", ctx.Request.Scheme);
Assert.Equal("/FeatureCollectionSetRequestFeatures", ctx.Request.PathBase);
ctx.Request.PathBase = "/base";
Assert.Equal("/base", ctx.Request.PathBase);
Assert.Equal("/path", ctx.Request.Path);
ctx.Request.Path = "/path";
Assert.Equal("/path", ctx.Request.Path);
Assert.Equal("?query", ctx.Request.QueryString.Value);
ctx.Request.QueryString = QueryString.Empty;
Assert.Equal("", ctx.Request.QueryString.Value);
Assert.Equal("HTTP/1.1", ctx.Request.Protocol);
ctx.Request.Protocol = "HTTP/1.0";
Assert.Equal("HTTP/1.0", ctx.Request.Protocol);
Assert.NotNull(ctx.Request.Headers);
var headers = new HeaderDictionary();
ctx.Features.Get<IHttpRequestFeature>().Headers = headers;
Assert.Same(headers, ctx.Features.Get<IHttpRequestFeature>().Headers);
Assert.NotNull(ctx.Request.Body);
var body = new MemoryStream();
ctx.Request.Body = body;
Assert.Same(body, ctx.Request.Body);
//Assert.NotNull(ctx.Features.Get<IHttpRequestIdentifierFeature>().TraceIdentifier);
//Assert.NotEqual(CancellationToken.None, ctx.RequestAborted);
//var token = new CancellationTokenSource().Token;
//ctx.RequestAborted = token;
//Assert.Equal(token, ctx.RequestAborted);
await ctx.Response.WriteAsync("Success");
return;
}
catch (Exception exception)
{
ctx.Response.StatusCode = 500;
await ctx.Response.WriteAsync(exception.ToString());
}
await ctx.Response.WriteAsync("_Failure");
}
private async Task FeatureCollectionSetResponseFeatures(HttpContext ctx)
{
try
{
Assert.Equal(200, ctx.Response.StatusCode);
ctx.Response.StatusCode = 404;
Assert.Equal(404, ctx.Response.StatusCode);
ctx.Response.StatusCode = 200;
Assert.Null(ctx.Features.Get<IHttpResponseFeature>().ReasonPhrase);
ctx.Features.Get<IHttpResponseFeature>().ReasonPhrase = "Set Response";
Assert.Equal("Set Response", ctx.Features.Get<IHttpResponseFeature>().ReasonPhrase);
Assert.NotNull(ctx.Response.Headers);
var headers = new HeaderDictionary();
ctx.Features.Get<IHttpResponseFeature>().Headers = headers;
Assert.Same(headers, ctx.Features.Get<IHttpResponseFeature>().Headers);
var originalBody = ctx.Response.Body;
Assert.NotNull(originalBody);
var body = new MemoryStream();
ctx.Response.Body = body;
Assert.Same(body, ctx.Response.Body);
ctx.Response.Body = originalBody;
await ctx.Response.WriteAsync("Success");
return;
}
catch (Exception exception)
{
ctx.Response.StatusCode = 500;
await ctx.Response.WriteAsync(exception.ToString());
}
await ctx.Response.WriteAsync("_Failure");
}
private async Task FeatureCollectionSetConnectionFeatures(HttpContext ctx)
{
try
{
Assert.True(IPAddress.IsLoopback(ctx.Connection.LocalIpAddress));
ctx.Connection.LocalIpAddress = IPAddress.IPv6Any;
Assert.Equal(IPAddress.IPv6Any, ctx.Connection.LocalIpAddress);
Assert.True(IPAddress.IsLoopback(ctx.Connection.RemoteIpAddress));
ctx.Connection.RemoteIpAddress = IPAddress.IPv6Any;
Assert.Equal(IPAddress.IPv6Any, ctx.Connection.RemoteIpAddress);
await ctx.Response.WriteAsync("Success");
return;
}
catch (Exception exception)
{
ctx.Response.StatusCode = 500;
await ctx.Response.WriteAsync(exception.ToString());
}
await ctx.Response.WriteAsync("_Failure");
}
private void Throw(HttpContext ctx)
{
throw new Exception();
}
private async Task SetCustomErorCode(HttpContext ctx)
{
var feature = ctx.Features.Get<IHttpResponseFeature>();
feature.ReasonPhrase = ctx.Request.Query["reason"];
feature.StatusCode = int.Parse(ctx.Request.Query["code"], CultureInfo.InvariantCulture);
if (ctx.Request.Query["writeBody"] == "True")
{
await ctx.Response.WriteAsync(ctx.Request.Query["body"]);
}
}
private async Task HelloWorld(HttpContext ctx)
{
if (ctx.Request.Path.Value.StartsWith("/Path", StringComparison.Ordinal))
{
await ctx.Response.WriteAsync(ctx.Request.Path.Value);
return;
}
if (ctx.Request.Path.Value.StartsWith("/Query", StringComparison.Ordinal))
{
await ctx.Response.WriteAsync(ctx.Request.QueryString.Value);
return;
}
await ctx.Response.WriteAsync("Hello World");
}
private async Task LargeResponseBody(HttpContext ctx)
{
if (int.TryParse(ctx.Request.Query["length"], out var length))
{
await ctx.Response.WriteAsync(new string('a', length));
}
}
#if !FORWARDCOMPAT
private Task UnflushedResponsePipe(HttpContext ctx)
{
var writer = ctx.Response.BodyWriter;
var memory = writer.GetMemory(10);
Assert.True(10 <= memory.Length);
writer.Advance(10);
return Task.CompletedTask;
}
private async Task FlushedPipeAndThenUnflushedPipe(HttpContext ctx)
{
var writer = ctx.Response.BodyWriter;
var memory = writer.GetMemory(10);
Assert.True(10 <= memory.Length);
writer.Advance(10);
await writer.FlushAsync();
memory = writer.GetMemory(10);
Assert.True(10 <= memory.Length);
writer.Advance(10);
}
#endif
private async Task ResponseHeaders(HttpContext ctx)
{
ctx.Response.Headers["UnknownHeader"] = "test123=foo";
ctx.Response.ContentType = "text/plain";
ctx.Response.Headers["MultiHeader"] = new StringValues(new string[] { "1", "2" });
await ctx.Response.WriteAsync("Request Complete");
}
private async Task ResponseEmptyHeaders(HttpContext ctx)
{
ctx.Response.Headers["EmptyHeader"] = "";
await ctx.Response.WriteAsync("EmptyHeaderShouldBeSkipped");
}
private Task TestRequestHeaders(HttpContext ctx)
{
// Test optimized and non-optimized headers behave equivalently
foreach (var headerName in new[] { "custom", "Content-Type" })
{
// StringValues.Empty.Equals(default(StringValues)), so we check if the implicit conversion
// to string[] returns null or Array.Empty<string>() to tell the difference.
if ((string[])ctx.Request.Headers[headerName] != Array.Empty<string>())
{
return ctx.Response.WriteAsync($"Failure: '{headerName}' indexer");
}
if (ctx.Request.Headers.TryGetValue(headerName, out var headerValue) || (string[])headerValue is not null)
{
return ctx.Response.WriteAsync($"Failure: '{headerName}' TryGetValue");
}
// Both default and StringValues.Empty should unset the header, allowing it to be added again.
ArgumentException duplicateKeyException = null;
ctx.Request.Headers.Add(headerName, "test");
ctx.Request.Headers[headerName] = default;
ctx.Request.Headers.Add(headerName, "test");
ctx.Request.Headers[headerName] = StringValues.Empty;
ctx.Request.Headers.Add(headerName, "test");
try
{
// Repeated adds should throw.
ctx.Request.Headers.Add(headerName, "test");
}
catch (ArgumentException ex)
{
duplicateKeyException = ex;
ctx.Request.Headers[headerName] = default;
}
if (duplicateKeyException is null)
{
return ctx.Response.WriteAsync($"Failure: Repeated '{headerName}' Add did not throw");
}
}
#if !FORWARDCOMPAT
if ((string[])ctx.Request.Headers.ContentType != Array.Empty<string>())
{
return ctx.Response.WriteAsync("Failure: ContentType property");
}
ctx.Request.Headers.ContentType = default;
if ((string[])ctx.Request.Headers.ContentType != Array.Empty<string>())
{
return ctx.Response.WriteAsync("Failure: ContentType property after setting default");
}
#endif
return ctx.Response.WriteAsync("Success");
}
private async Task ResponseInvalidOrdering(HttpContext ctx)
{
if (ctx.Request.Path.Equals("/SetStatusCodeAfterWrite"))
{
await ctx.Response.WriteAsync("Started_");
try
{
ctx.Response.StatusCode = 200;
}
catch (InvalidOperationException)
{
await ctx.Response.WriteAsync("SetStatusCodeAfterWriteThrew_");
}
await ctx.Response.WriteAsync("Finished");
return;
}
else if (ctx.Request.Path.Equals("/SetHeaderAfterWrite"))
{
await ctx.Response.WriteAsync("Started_");
try
{
ctx.Response.Headers["This will fail"] = "some value";
}
catch (InvalidOperationException)
{
await ctx.Response.WriteAsync("SetHeaderAfterWriteThrew_");
}
await ctx.Response.WriteAsync("Finished");
return;
}
}
private async Task ReadAndWriteSynchronously(HttpContext ctx)
{
var t2 = Task.Run(() => WriteManyTimesToResponseBody(ctx));
var t1 = Task.Run(() => ReadRequestBody(ctx));
await Task.WhenAll(t1, t2);
}
private async Task ReadRequestBody(HttpContext ctx)
{
#if !FORWARDCOMPAT
Assert.True(ctx.Request.CanHaveBody());
#endif
var readBuffer = new byte[1];
var result = await ctx.Request.Body.ReadAsync(readBuffer, 0, 1);
while (result != 0)
{
result = await ctx.Request.Body.ReadAsync(readBuffer, 0, 1);
}
}
private async Task ReadRequestBodyLarger(HttpContext ctx)
{
#if !FORWARDCOMPAT
Assert.True(ctx.Request.CanHaveBody());
#endif
var readBuffer = new byte[4096];
var result = await ctx.Request.Body.ReadAsync(readBuffer, 0, 4096);
while (result != 0)
{
result = await ctx.Request.Body.ReadAsync(readBuffer, 0, 4096);
}
}
private int _requestsInFlight = 0;
private async Task ReadAndCountRequestBody(HttpContext ctx)
{
Interlocked.Increment(ref _requestsInFlight);
await ctx.Response.WriteAsync(_requestsInFlight.ToString(CultureInfo.InvariantCulture));
var readBuffer = new byte[1];
await ctx.Request.Body.ReadAsync(readBuffer, 0, 1);
await ctx.Response.WriteAsync("done");
Interlocked.Decrement(ref _requestsInFlight);
}
private async Task WaitForAppToStartShuttingDown(HttpContext ctx)
{
await ctx.Response.WriteAsync("test1");
#if FORWARDCOMPAT
var lifetime = ctx.RequestServices.GetService<Microsoft.AspNetCore.Hosting.IApplicationLifetime>();
#else
var lifetime = ctx.RequestServices.GetService<IHostApplicationLifetime>();
#endif
lifetime.ApplicationStopping.WaitHandle.WaitOne();
await ctx.Response.WriteAsync("test2");
}
private async Task ReadFullBody(HttpContext ctx)
{
#if !FORWARDCOMPAT
Assert.True(ctx.Request.CanHaveBody());
#endif
await ReadRequestBody(ctx);
ctx.Response.ContentLength = 9;
await ctx.Response.WriteAsync("Completed");
}
private async Task WriteManyTimesToResponseBody(HttpContext ctx)
{
for (var i = 0; i < 10000; i++)
{
await ctx.Response.WriteAsync("hello world");
}
}
private async Task ReadAndWriteEcho(HttpContext ctx)
{
#if !FORWARDCOMPAT
Assert.True(ctx.Request.CanHaveBody());
#endif
var readBuffer = new byte[4096];
var result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length);
while (result != 0)
{
await ctx.Response.WriteAsync(Encoding.UTF8.GetString(readBuffer, 0, result));
result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length);
}
}
private async Task ReadAndFlushEcho(HttpContext ctx)
{
#if !FORWARDCOMPAT
Assert.True(ctx.Request.CanHaveBody());
#endif
var readBuffer = new byte[4096];
var result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length);
while (result != 0)
{
await ctx.Response.WriteAsync(Encoding.UTF8.GetString(readBuffer, 0, result));
await ctx.Response.Body.FlushAsync();
result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length);
}
}
private async Task ReadAndWriteEchoLines(HttpContext ctx)
{
#if !FORWARDCOMPAT
Assert.True(ctx.Request.CanHaveBody());
#endif
if (ctx.Request.Headers.TryGetValue("Response-Content-Type", out var contentType))
{
ctx.Response.ContentType = contentType;
}
//Send headers
await ctx.Response.Body.FlushAsync();
var reader = new StreamReader(ctx.Request.Body);
while (true)
{
var line = await reader.ReadLineAsync();
if (line == null)
{
return;
}
await ctx.Response.WriteAsync(line + Environment.NewLine);
await ctx.Response.Body.FlushAsync();
}
}
private async Task ReadAndWriteEchoLinesNoBuffering(HttpContext ctx)
{
#if FORWARDCOMPAT
var feature = ctx.Features.Get<IHttpBufferingFeature>();
feature.DisableResponseBuffering();
#else
var feature = ctx.Features.Get<IHttpResponseBodyFeature>();
feature.DisableBuffering();
Assert.True(ctx.Request.CanHaveBody());
#endif
if (ctx.Request.Headers.TryGetValue("Response-Content-Type", out var contentType))
{
ctx.Response.ContentType = contentType;
}
//Send headers
await ctx.Response.Body.FlushAsync();
var reader = new StreamReader(ctx.Request.Body);
while (true)
{
var line = await reader.ReadLineAsync();
if (line == null)
{
return;
}
await ctx.Response.WriteAsync(line + Environment.NewLine);
}
}
private async Task ReadPartialBody(HttpContext ctx)
{
#if !FORWARDCOMPAT
Assert.True(ctx.Request.CanHaveBody());
#endif
var data = new byte[5];
var count = 0;
do
{
count += await ctx.Request.Body.ReadAsync(data, count, data.Length - count);
} while (count != data.Length);
await ctx.Response.Body.WriteAsync(data, 0, data.Length);
}
private async Task SetHeaderFromBody(HttpContext ctx)
{
using (var reader = new StreamReader(ctx.Request.Body))
{
var value = await reader.ReadToEndAsync();
ctx.Response.Headers["BodyAsString"] = value;
await ctx.Response.WriteAsync(value);
}
}
private async Task ReadAndWriteEchoTwice(HttpContext ctx)
{
var readBuffer = new byte[4096];
var result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length);
while (result != 0)
{
await ctx.Response.WriteAsync(Encoding.UTF8.GetString(readBuffer, 0, result));
await ctx.Response.Body.FlushAsync();
await ctx.Response.WriteAsync(Encoding.UTF8.GetString(readBuffer, 0, result));
await ctx.Response.Body.FlushAsync();
result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length);
}
}
private async Task ReadAndWriteSlowConnection(HttpContext ctx)
{
var t2 = Task.Run(() => WriteResponseBodyAFewTimes(ctx));
var t1 = Task.Run(() => ReadRequestBody(ctx));
await Task.WhenAll(t1, t2);
}
private async Task WriteResponseBodyAFewTimes(HttpContext ctx)
{
for (var i = 0; i < 100; i++)
{
await ctx.Response.WriteAsync("hello world");
}
}
private async Task ReadAndWriteCopyToAsync(HttpContext ctx)
{
#if !FORWARDCOMPAT
Assert.True(ctx.Request.CanHaveBody());
#endif
await ctx.Request.Body.CopyToAsync(ctx.Response.Body);
}
private async Task TestReadOffsetWorks(HttpContext ctx)
{
var buffer = new byte[11];
await ctx.Request.Body.ReadAsync(buffer, 0, 6);
await ctx.Request.Body.ReadAsync(buffer, 6, 5);
await ctx.Response.WriteAsync(Encoding.UTF8.GetString(buffer));
}
private async Task TestInvalidReadOperations(HttpContext ctx)
{
var success = false;
if (ctx.Request.Path.StartsWithSegments("/NullBuffer"))
{
try
{
await ctx.Request.Body.ReadAsync(null, 0, 0);
}
catch (Exception)
{
success = true;
}
}
else if (ctx.Request.Path.StartsWithSegments("/InvalidOffsetSmall"))
{
try
{
await ctx.Request.Body.ReadAsync(new byte[1], -1, 0);
}
catch (ArgumentOutOfRangeException)
{
success = true;
}
}
else if (ctx.Request.Path.StartsWithSegments("/InvalidOffsetLarge"))
{
try
{
await ctx.Request.Body.ReadAsync(new byte[1], 2, 0);
}
catch (ArgumentOutOfRangeException)
{
success = true;
}
}
else if (ctx.Request.Path.StartsWithSegments("/InvalidCountSmall"))
{
try
{
await ctx.Request.Body.ReadAsync(new byte[1], 0, -1);
}
catch (ArgumentOutOfRangeException)
{
success = true;
}
}
else if (ctx.Request.Path.StartsWithSegments("/InvalidCountLarge"))
{
try
{
await ctx.Request.Body.ReadAsync(new byte[1], 0, -1);
}
catch (ArgumentOutOfRangeException)
{
success = true;
}
}
else if (ctx.Request.Path.StartsWithSegments("/InvalidCountWithOffset"))
{
try
{
await ctx.Request.Body.ReadAsync(new byte[3], 1, 3);
}
catch (ArgumentOutOfRangeException)
{
success = true;
}
}
await ctx.Response.WriteAsync(success ? "Success" : "Failure");
}
private async Task TestValidReadOperations(HttpContext ctx)
{
var count = -1;
if (ctx.Request.Path.StartsWithSegments("/NullBuffer"))
{
count = await ctx.Request.Body.ReadAsync(null, 0, 0);
}
else if (ctx.Request.Path.StartsWithSegments("/NullBufferPost"))
{
count = await ctx.Request.Body.ReadAsync(null, 0, 0);
}
else if (ctx.Request.Path.StartsWithSegments("/InvalidCountZeroRead"))
{
count = await ctx.Request.Body.ReadAsync(new byte[1], 0, 0);
}
else if (ctx.Request.Path.StartsWithSegments("/InvalidCountZeroReadPost"))
{
count = await ctx.Request.Body.ReadAsync(new byte[1], 0, 0);
}
await ctx.Response.WriteAsync(count == 0 ? "Success" : "Failure");
}
private async Task TestInvalidWriteOperations(HttpContext ctx)
{
var success = false;
if (ctx.Request.Path.StartsWithSegments("/InvalidOffsetSmall"))
{
try
{
await ctx.Response.Body.WriteAsync(new byte[1], -1, 0);
}
catch (ArgumentOutOfRangeException)
{
success = true;
}
}
else if (ctx.Request.Path.StartsWithSegments("/InvalidOffsetLarge"))
{
try
{
await ctx.Response.Body.WriteAsync(new byte[1], 2, 0);
}
catch (ArgumentOutOfRangeException)
{
success = true;
}
}
else if (ctx.Request.Path.StartsWithSegments("/InvalidCountSmall"))
{
try
{
await ctx.Response.Body.WriteAsync(new byte[1], 0, -1);
}
catch (ArgumentOutOfRangeException)
{
success = true;
}
}
else if (ctx.Request.Path.StartsWithSegments("/InvalidCountLarge"))
{
try
{
await ctx.Response.Body.WriteAsync(new byte[1], 0, -1);
}
catch (ArgumentOutOfRangeException)
{
success = true;
}
}
else if (ctx.Request.Path.StartsWithSegments("/InvalidCountWithOffset"))
{
try
{
await ctx.Response.Body.WriteAsync(new byte[3], 1, 3);
}
catch (ArgumentOutOfRangeException)
{
success = true;
}
}
await ctx.Response.WriteAsync(success ? "Success" : "Failure");
}
private async Task TestValidWriteOperations(HttpContext ctx)
{
if (ctx.Request.Path.StartsWithSegments("/NullBuffer"))
{
await ctx.Response.Body.WriteAsync(null, 0, 0);
}
else if (ctx.Request.Path.StartsWithSegments("/NullBufferPost"))
{
await ctx.Response.Body.WriteAsync(null, 0, 0);
}
await ctx.Response.WriteAsync("Success");
}
private async Task LargeResponseFile(HttpContext ctx)
{
var tempFile = System.IO.Path.GetTempFileName();
var fileContent = new string('a', 200000);
var fileStream = File.OpenWrite(tempFile);
for (var i = 0; i < 1000; i++)
{
await fileStream.WriteAsync(Encoding.UTF8.GetBytes(fileContent), 0, fileContent.Length);
}
fileStream.Close();
await ctx.Response.SendFileAsync(tempFile, 0, null);
// Try to delete the file from the temp directory. If it fails, don't report an error
// to the application. File should eventually be cleaned up from the temp directory
// by OS.
try
{
File.Delete(tempFile);
}
catch (Exception)
{
}
}
private async Task BasePath(HttpContext ctx)
{
await ctx.Response.WriteAsync(AppDomain.CurrentDomain.BaseDirectory);
}
private Task RequestPath(HttpContext ctx)
{
ctx.Request.Headers.ContentLength = ctx.Request.Path.Value.Length;
return ctx.Response.WriteAsync(ctx.Request.Path.Value);
}
private async Task Shutdown(HttpContext ctx)
{
await ctx.Response.WriteAsync("Shutting down");
#if FORWARDCOMPAT
ctx.RequestServices.GetService<Microsoft.AspNetCore.Hosting.IApplicationLifetime>().StopApplication();
#else
ctx.RequestServices.GetService<IHostApplicationLifetime>().StopApplication();
#endif
}
private async Task ShutdownStopAsync(HttpContext ctx)
{
await ctx.Response.WriteAsync("Shutting down");
var server = ctx.RequestServices.GetService<IServer>();
await server.StopAsync(default);
}
private async Task ShutdownStopAsyncWithCancelledToken(HttpContext ctx)
{
await ctx.Response.WriteAsync("Shutting down");
var server = ctx.RequestServices.GetService<IServer>();
var cts = new CancellationTokenSource();
cts.Cancel();
await server.StopAsync(cts.Token);
}
private async Task StackSize(HttpContext ctx)
{
// This would normally stackoverflow if we didn't increase the stack size per thread.
RecursiveFunction(10000);
await ctx.Response.WriteAsync("Hello World");
}
private async Task StackSizeLarge(HttpContext ctx)
{
// This would normally stackoverflow if we didn't increase the stack size per thread.
RecursiveFunction(30000);
await ctx.Response.WriteAsync("Hello World");
}
private void RecursiveFunction(int i)
{
if (i == 0)
{
return;
}
RecursiveFunction(i - 1);
}
private async Task StartupHook(HttpContext ctx)
{
await ctx.Response.WriteAsync(StartupHookCalled.ToString());
}
private async Task GetServerVariableStress(HttpContext ctx)
{
// This test simulates the scenario where native Flush call is being
// executed on background thread while request thread calls GetServerVariable
// concurrent native calls may cause native object corruption
var serverVariableFeature = ctx.Features.Get<IServerVariablesFeature>();
await ctx.Response.WriteAsync("Response Begin");
for (int i = 0; i < 1000; i++)
{
await ctx.Response.WriteAsync(serverVariableFeature["REMOTE_PORT"]);
await ctx.Response.Body.FlushAsync();
}
await ctx.Response.WriteAsync("Response End");
}
private async Task CommandLineArgs(HttpContext ctx)
{
await ctx.Response.WriteAsync(string.Join("|", Environment.GetCommandLineArgs().Skip(1)));
}
public Task HttpsHelloWorld(HttpContext ctx) =>
ctx.Response.WriteAsync("Scheme:" + ctx.Request.Scheme + "; Original:" + ctx.Request.Headers["x-original-proto"]);
public Task Path(HttpContext ctx) => ctx.Response.WriteAsync(ctx.Request.Path.Value);
public Task Query(HttpContext ctx) => ctx.Response.WriteAsync(ctx.Request.QueryString.Value);
public Task BodyLimit(HttpContext ctx) => ctx.Response.WriteAsync(ctx.Features.Get<IHttpMaxRequestBodySizeFeature>()?.MaxRequestBodySize?.ToString(CultureInfo.InvariantCulture) ?? "null");
public Task Anonymous(HttpContext context) => context.Response.WriteAsync("Anonymous?" + !context.User.Identity.IsAuthenticated);
public Task Restricted(HttpContext context)
{
if (context.User.Identity.IsAuthenticated)
{
Assert.IsType<WindowsPrincipal>(context.User);
return context.Response.WriteAsync(context.User.Identity.AuthenticationType);
}
else
{
return context.ChallengeAsync(IISDefaults.AuthenticationScheme);
}
}
public Task Forbidden(HttpContext context) => context.ForbidAsync(IISDefaults.AuthenticationScheme);
public Task RestrictedNTLM(HttpContext context)
{
if (string.Equals("NTLM", context.User.Identity.AuthenticationType, StringComparison.Ordinal))
{
return context.Response.WriteAsync("NTLM");
}
else
{
return context.ChallengeAsync(IISDefaults.AuthenticationScheme);
}
}
public Task UpgradeFeatureDetection(HttpContext context) =>
context.Response.WriteAsync(context.Features.Get<IHttpUpgradeFeature>() != null ? "Enabled" : "Disabled");
public Task CheckRequestHandlerVersion(HttpContext context)
{
// We need to check if the aspnetcorev2_outofprocess dll is loaded by iisexpress.exe
// As they aren't in the same process, we will try to delete the file and expect a file
// in use error
try
{
File.Delete(context.Request.Headers["ANCMRHPath"]);
}
catch (UnauthorizedAccessException)
{
// TODO calling delete on the file will succeed when running with IIS
return context.Response.WriteAsync("Hello World");
}
return context.Response.WriteAsync(context.Request.Headers["ANCMRHPath"]);
}
private async Task ProcessId(HttpContext context)
{
await context.Response.WriteAsync(Environment.ProcessId.ToString(CultureInfo.InvariantCulture));
}
public async Task ANCM_HTTPS_PORT(HttpContext context)
{
var httpsPort = context.RequestServices.GetService<IConfiguration>().GetValue<int?>("ANCM_HTTPS_PORT");
await context.Response.WriteAsync(httpsPort.HasValue ? httpsPort.Value.ToString(CultureInfo.InvariantCulture) : "NOVALUE");
}
public async Task HTTPS_PORT(HttpContext context)
{
var httpsPort = context.RequestServices.GetService<IConfiguration>().GetValue<int?>("HTTPS_PORT");
await context.Response.WriteAsync(httpsPort.HasValue ? httpsPort.Value.ToString(CultureInfo.InvariantCulture) : "NOVALUE");
}
public Task Latin1(HttpContext context)
{
var value = context.Request.Headers["foo"];
Assert.Equal("£", value);
return Task.CompletedTask;
}
public Task InvalidCharacter(HttpContext context)
{
var value = context.Request.Headers["foo"];
Assert.Equal("�", value);
return Task.CompletedTask;
}
private async Task TransferEncodingHeadersWithMultipleValues(HttpContext ctx)
{
try
{
#if !FORWARDCOMPAT
Assert.True(ctx.Request.CanHaveBody());
#endif
Assert.True(ctx.Request.Headers.ContainsKey("Transfer-Encoding"));
Assert.Equal("gzip, chunked", ctx.Request.Headers["Transfer-Encoding"]);
return;
}
catch (Exception exception)
{
ctx.Response.StatusCode = 500;
await ctx.Response.WriteAsync(exception.ToString());
}
}
private async Task TransferEncodingAndContentLengthShouldBeRemove(HttpContext ctx)
{
try
{
#if !FORWARDCOMPAT
Assert.True(ctx.Request.CanHaveBody());
#endif
Assert.True(ctx.Request.Headers.ContainsKey("Transfer-Encoding"));
Assert.Equal("gzip, chunked", ctx.Request.Headers["Transfer-Encoding"]);
Assert.False(ctx.Request.Headers.ContainsKey("Content-Length"));
Assert.True(ctx.Request.Headers.ContainsKey("X-Content-Length"));
Assert.Equal("5", ctx.Request.Headers["X-Content-Length"]);
return;
}
catch (Exception exception)
{
ctx.Response.StatusCode = 500;
await ctx.Response.WriteAsync(exception.ToString());
}
}
#if !FORWARDCOMPAT
public Task ResponseTrailers_HTTP2_TrailersAvailable(HttpContext context)
{
Assert.Equal("HTTP/2", context.Request.Protocol);
Assert.True(context.Response.SupportsTrailers());
return Task.FromResult(0);
}
public Task ResponseTrailers_HTTP1_TrailersNotAvailable(HttpContext context)
{
Assert.Equal("HTTP/1.1", context.Request.Protocol);
Assert.False(context.Response.SupportsTrailers());
return Task.FromResult(0);
}
public Task ResponseTrailers_ProhibitedTrailers_Blocked(HttpContext context)
{
Assert.True(context.Response.SupportsTrailers());
foreach (var header in DisallowedTrailers)
{
Assert.Throws<InvalidOperationException>(() => context.Response.AppendTrailer(header, "value"));
}
return Task.FromResult(0);
}
public Task ResponseTrailers_NoBody_TrailersSent(HttpContext context)
{
context.Response.DeclareTrailer("trailername");
context.Response.AppendTrailer("trailername", "TrailerValue");
return Task.FromResult(0);
}
public async Task ResponseTrailers_WithBody_TrailersSent(HttpContext context)
{
await context.Response.WriteAsync("Hello World");
context.Response.AppendTrailer("TrailerName", "Trailer Value");
}
public async Task ResponseTrailers_WithContentLengthBody_TrailersSent(HttpContext context)
{
var body = "Hello World";
context.Response.ContentLength = body.Length;
await context.Response.WriteAsync(body);
context.Response.AppendTrailer("TrailerName", "Trailer Value");
}
public async Task ResponseTrailers_WithTrailersBeforeContentLengthBody_TrailersSent(HttpContext context)
{
var body = "Hello World";
context.Response.ContentLength = body.Length * 2;
await context.Response.WriteAsync(body);
context.Response.AppendTrailer("TrailerName", "Trailer Value");
await context.Response.WriteAsync(body);
}
public async Task ResponseTrailers_WithContentLengthBodyAndDeclared_TrailersSent(HttpContext context)
{
var body = "Hello World";
context.Response.ContentLength = body.Length;
context.Response.DeclareTrailer("TrailerName");
await context.Response.WriteAsync(body);
context.Response.AppendTrailer("TrailerName", "Trailer Value");
}
public async Task ResponseTrailers_WithContentLengthBodyAndDeclaredButMissingTrailers_Completes(HttpContext context)
{
var body = "Hello World";
context.Response.ContentLength = body.Length;
context.Response.DeclareTrailer("TrailerName");
await context.Response.WriteAsync(body);
}
public Task ResponseTrailers_MultipleValues_SentAsSeparateHeaders(HttpContext context)
{
context.Response.AppendTrailer("trailername", new StringValues(new[] { "TrailerValue0", "TrailerValue1" }));
return Task.FromResult(0);
}
public Task ResponseTrailers_LargeTrailers_Success(HttpContext context)
{
var values = new[] {
new string('a', 1024),
new string('b', 1024 * 4),
new string('c', 1024 * 8),
new string('d', 1024 * 16),
new string('e', 1024 * 32),
new string('f', 1024 * 64 - 1) }; // Max header size
context.Response.AppendTrailer("ThisIsALongerHeaderNameThatStillWorksForReals", new StringValues(values));
return Task.FromResult(0);
}
public Task ResponseTrailers_NullValues_Ignored(HttpContext context)
{
foreach (var kvp in NullTrailers)
{
context.Response.AppendTrailer(kvp.Item1, kvp.Item2);
}
return Task.FromResult(0);
}
public Task AppException_BeforeResponseHeaders_500(HttpContext context)
{
throw new Exception("Application exception");
}
public async Task AppException_AfterHeaders_PriorOSVersions_ResetCancel(HttpContext httpContext)
{
await httpContext.Response.Body.FlushAsync();
throw new Exception("Application exception");
}
public async Task AppException_AfterHeaders_ResetInternalError(HttpContext httpContext)
{
await httpContext.Response.Body.FlushAsync();
throw new Exception("Application exception");
}
public Task Reset_PriorOSVersions_NotSupported(HttpContext httpContext)
{
Assert.Equal("HTTP/2", httpContext.Request.Protocol);
var feature = httpContext.Features.Get<IHttpResetFeature>();
Assert.Null(feature);
return httpContext.Response.WriteAsync("Hello World");
}
public Task Reset_Http1_NotSupported(HttpContext httpContext)
{
Assert.Equal("HTTP/1.1", httpContext.Request.Protocol);
var feature = httpContext.Features.Get<IHttpResetFeature>();
Assert.Null(feature);
return httpContext.Response.WriteAsync("Hello World");
}
private TaskCompletionSource _resetBeforeResponseResetsCts = new TaskCompletionSource();
public Task Reset_BeforeResponse_Resets(HttpContext httpContext)
{
try
{
Assert.Equal("HTTP/2", httpContext.Request.Protocol);
var feature = httpContext.Features.Get<IHttpResetFeature>();
Assert.NotNull(feature);
feature.Reset(1111); // Custom
_resetBeforeResponseResetsCts.SetResult();
}
catch (Exception ex)
{
_resetBeforeResponseResetsCts.SetException(ex);
}
return Task.FromResult(0);
}
public async Task Reset_BeforeResponse_Resets_Complete(HttpContext httpContext)
{
await _resetBeforeResponseResetsCts.Task;
}
private TaskCompletionSource _resetBeforeResponseZeroResetsCts = new TaskCompletionSource();
public Task Reset_BeforeResponse_Zero_Resets(HttpContext httpContext)
{
try
{
Assert.Equal("HTTP/2", httpContext.Request.Protocol);
var feature = httpContext.Features.Get<IHttpResetFeature>();
Assert.NotNull(feature);
feature.Reset(0); // Zero should be an allowed errorCode
_resetBeforeResponseZeroResetsCts.SetResult();
}
catch (Exception ex)
{
_resetBeforeResponseZeroResetsCts.SetException(ex);
}
return Task.FromResult(0);
}
public async Task Reset_BeforeResponse_Resets_Zero_Complete(HttpContext httpContext)
{
await _resetBeforeResponseZeroResetsCts.Task;
}
private TaskCompletionSource _resetAfterResponseHeadersResetsCts = new TaskCompletionSource();
public async Task Reset_AfterResponseHeaders_Resets(HttpContext httpContext)
{
try
{
Assert.Equal("HTTP/2", httpContext.Request.Protocol);
var feature = httpContext.Features.Get<IHttpResetFeature>();
Assert.NotNull(feature);
await httpContext.Response.Body.FlushAsync();
feature.Reset(1111); // Custom
_resetAfterResponseHeadersResetsCts.SetResult();
}
catch (Exception ex)
{
_resetAfterResponseHeadersResetsCts.SetException(ex);
}
}
public async Task Reset_AfterResponseHeaders_Resets_Complete(HttpContext httpContext)
{
await _resetAfterResponseHeadersResetsCts.Task;
}
private TaskCompletionSource _resetDuringResponseBodyResetsCts = new TaskCompletionSource();
public async Task Reset_DuringResponseBody_Resets(HttpContext httpContext)
{
try
{
Assert.Equal("HTTP/2", httpContext.Request.Protocol);
var feature = httpContext.Features.Get<IHttpResetFeature>();
Assert.NotNull(feature);
await httpContext.Response.WriteAsync("Hello World");
await httpContext.Response.Body.FlushAsync();
feature.Reset(1111); // Custom
_resetDuringResponseBodyResetsCts.SetResult();
}
catch (Exception ex)
{
_resetDuringResponseBodyResetsCts.SetException(ex);
}
}
public async Task Reset_DuringResponseBody_Resets_Complete(HttpContext httpContext)
{
await _resetDuringResponseBodyResetsCts.Task;
}
private TaskCompletionSource _resetBeforeRequestBodyResetsCts = new TaskCompletionSource();
public async Task Reset_BeforeRequestBody_Resets(HttpContext httpContext)
{
try
{
Assert.Equal("HTTP/2", httpContext.Request.Protocol);
var feature = httpContext.Features.Get<IHttpResetFeature>();
Assert.NotNull(feature);
var readTask = httpContext.Request.Body.ReadAsync(new byte[10], 0, 10);
feature.Reset(1111);
await Assert.ThrowsAsync<IOException>(() => readTask);
_resetBeforeRequestBodyResetsCts.SetResult();
}
catch (Exception ex)
{
_resetBeforeRequestBodyResetsCts.SetException(ex);
}
}
public async Task Reset_BeforeRequestBody_Resets_Complete(HttpContext httpContext)
{
await _resetBeforeRequestBodyResetsCts.Task;
}
private TaskCompletionSource _resetDuringRequestBodyResetsCts = new TaskCompletionSource();
public async Task Reset_DuringRequestBody_Resets(HttpContext httpContext)
{
try
{
Assert.Equal("HTTP/2", httpContext.Request.Protocol);
var feature = httpContext.Features.Get<IHttpResetFeature>();
Assert.NotNull(feature);
#if !FORWARDCOMPAT
Assert.True(httpContext.Request.CanHaveBody());
#endif
var read = await httpContext.Request.Body.ReadAsync(new byte[10], 0, 10);
Assert.Equal(10, read);
var readTask = httpContext.Request.Body.ReadAsync(new byte[10], 0, 10);
feature.Reset(1111);
await Assert.ThrowsAsync<IOException>(() => readTask);
_resetDuringRequestBodyResetsCts.SetResult();
}
catch (Exception ex)
{
_resetDuringRequestBodyResetsCts.SetException(ex);
}
}
public Task Goaway(HttpContext httpContext)
{
httpContext.Response.Headers["Connection"] = "close";
return Task.CompletedTask;
}
public Task ConnectionRequestClose(HttpContext httpContext)
{
httpContext.Connection.RequestClose();
return Task.CompletedTask;
}
private TaskCompletionSource _completeAsync = new TaskCompletionSource();
public async Task CompleteAsync(HttpContext httpContext)
{
await httpContext.Response.CompleteAsync();
await _completeAsync.Task;
}
public Task CompleteAsync_Completed(HttpContext httpContext)
{
_completeAsync.TrySetResult();
return Task.CompletedTask;
}
public async Task Reset_DuringRequestBody_Resets_Complete(HttpContext httpContext)
{
await _resetDuringRequestBodyResetsCts.Task;
}
private TaskCompletionSource<object> _onCompletedHttpContext = new TaskCompletionSource<object>();
public async Task OnCompletedHttpContext(HttpContext context)
{
// This shouldn't block the response or the server from shutting down.
context.Response.OnCompleted(async () =>
{
var context = _httpContextAccessor.HttpContext;
await Task.Delay(500);
// Access all fields of the connection after final flush.
try
{
_ = context.Connection.RemoteIpAddress;
_ = context.Connection.LocalIpAddress;
_ = context.Connection.Id;
_ = context.Connection.ClientCertificate;
_ = context.Connection.LocalPort;
_ = context.Connection.RemotePort;
_ = context.Request.ContentLength;
_ = context.Request.Headers;
_ = context.Request.Query;
_ = context.Request.Body;
_ = context.Request.ContentType;
_ = context.Response.StatusCode;
_ = context.Response.Body;
_ = context.Response.Headers;
_ = context.Response.ContentType;
}
catch (Exception ex)
{
_onCompletedHttpContext.TrySetResult(ex);
}
_onCompletedHttpContext.TrySetResult(null);
});
await context.Response.WriteAsync("SlowOnCompleted");
}
public async Task OnCompletedHttpContext_Completed(HttpContext httpContext)
{
await _onCompletedHttpContext.Task;
}
private TaskCompletionSource _responseTrailers_CompleteAsyncNoBody_TrailersSent = new TaskCompletionSource();
public async Task ResponseTrailers_CompleteAsyncNoBody_TrailersSent(HttpContext httpContext)
{
httpContext.Response.AppendTrailer("trailername", "TrailerValue");
await httpContext.Response.CompleteAsync();
await _responseTrailers_CompleteAsyncNoBody_TrailersSent.Task;
}
public Task ResponseTrailers_CompleteAsyncNoBody_TrailersSent_Completed(HttpContext httpContext)
{
_responseTrailers_CompleteAsyncNoBody_TrailersSent.TrySetResult();
return Task.CompletedTask;
}
private TaskCompletionSource _responseTrailers_CompleteAsyncWithBody_TrailersSent = new TaskCompletionSource();
public async Task ResponseTrailers_CompleteAsyncWithBody_TrailersSent(HttpContext httpContext)
{
await httpContext.Response.WriteAsync("Hello World");
httpContext.Response.AppendTrailer("TrailerName", "Trailer Value");
await httpContext.Response.CompleteAsync();
await _responseTrailers_CompleteAsyncWithBody_TrailersSent.Task;
}
public Task ResponseTrailers_CompleteAsyncWithBody_TrailersSent_Completed(HttpContext httpContext)
{
_responseTrailers_CompleteAsyncWithBody_TrailersSent.TrySetResult();
return Task.CompletedTask;
}
public async Task Reset_AfterCompleteAsync_NoReset(HttpContext httpContext)
{
Assert.Equal("HTTP/2", httpContext.Request.Protocol);
var feature = httpContext.Features.Get<IHttpResetFeature>();
Assert.NotNull(feature);
await httpContext.Response.WriteAsync("Hello World");
await httpContext.Response.CompleteAsync();
// The request and response are fully complete, the reset doesn't get sent.
feature.Reset(1111);
}
public async Task Reset_CompleteAsyncDuringRequestBody_Resets(HttpContext httpContext)
{
Assert.Equal("HTTP/2", httpContext.Request.Protocol);
var feature = httpContext.Features.Get<IHttpResetFeature>();
Assert.NotNull(feature);
var read = await httpContext.Request.Body.ReadAsync(new byte[10], 0, 10);
Assert.Equal(10, read);
var readTask = httpContext.Request.Body.ReadAsync(new byte[10], 0, 10);
await httpContext.Response.CompleteAsync();
feature.Reset((int)0); // GRPC does this
await Assert.ThrowsAsync<IOException>(() => readTask);
}
public Task Http2_MethodsRequestWithoutData_Success(HttpContext httpContext)
{
Assert.Equal("HTTP/2", httpContext.Request.Protocol);
#if !FORWARDCOMPAT
Assert.False(httpContext.Request.CanHaveBody());
var feature = httpContext.Features.Get<IHttpUpgradeFeature>();
// The upgrade feature won't be present if WebSockets aren't enabled in IIS.
// IsUpgradableRequest should always return false for HTTP/2.
Assert.False(feature?.IsUpgradableRequest ?? false);
#endif
Assert.Null(httpContext.Request.ContentLength);
Assert.False(httpContext.Request.Headers.ContainsKey(HeaderNames.TransferEncoding));
return Task.CompletedTask;
}
public Task Http2_RequestWithDataAndContentLength_Success(HttpContext httpContext)
{
Assert.Equal("HTTP/2", httpContext.Request.Protocol);
#if !FORWARDCOMPAT
Assert.True(httpContext.Request.CanHaveBody());
#endif
Assert.Equal(11, httpContext.Request.ContentLength);
Assert.False(httpContext.Request.Headers.ContainsKey(HeaderNames.TransferEncoding));
return httpContext.Request.Body.CopyToAsync(httpContext.Response.Body);
}
public Task Http2_RequestWithDataAndNoContentLength_Success(HttpContext httpContext)
{
Assert.Equal("HTTP/2", httpContext.Request.Protocol);
#if !FORWARDCOMPAT
Assert.True(httpContext.Request.CanHaveBody());
#endif
Assert.Null(httpContext.Request.ContentLength);
// The client didn't send this header, Http.Sys added it for back compat with HTTP/1.1.
Assert.Equal("chunked", httpContext.Request.Headers.TransferEncoding);
return httpContext.Request.Body.CopyToAsync(httpContext.Response.Body);
}
public Task Http2_ResponseWithData_Success(HttpContext httpContext)
{
Assert.Equal("HTTP/2", httpContext.Request.Protocol);
return httpContext.Response.WriteAsync("Hello World");
}
public Task IncreaseRequestLimit(HttpContext httpContext)
{
var maxRequestBodySizeFeature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
maxRequestBodySizeFeature.MaxRequestBodySize = 2;
return Task.CompletedTask;
}
public Task OnCompletedThrows(HttpContext httpContext)
{
httpContext.Response.OnCompleted(() =>
{
throw new Exception();
});
return Task.CompletedTask;
}
public Task Http3_Direct(HttpContext context)
{
try
{
Assert.True(context.Request.IsHttps);
return context.Response.WriteAsync(context.Request.Protocol);
}
catch (Exception ex)
{
return context.Response.WriteAsync(ex.ToString());
}
}
public Task Http3_AltSvcHeader_UpgradeFromHttp1(HttpContext context)
{
var altsvc = $@"h3="":{context.Connection.LocalPort}""";
try
{
Assert.True(context.Request.IsHttps);
context.Response.Headers.AltSvc = altsvc;
return context.Response.WriteAsync(context.Request.Protocol);
}
catch (Exception ex)
{
return context.Response.WriteAsync(ex.ToString());
}
}
public Task Http3_AltSvcHeader_UpgradeFromHttp2(HttpContext context)
{
return Http3_AltSvcHeader_UpgradeFromHttp1(context);
}
public async Task Http3_ResponseTrailers(HttpContext context)
{
try
{
Assert.True(context.Request.IsHttps);
await context.Response.WriteAsync(context.Request.Protocol);
context.Response.AppendTrailer("custom", "value");
}
catch (Exception ex)
{
await context.Response.WriteAsync(ex.ToString());
}
}
public Task Http3_ResetBeforeHeaders(HttpContext context)
{
try
{
Assert.True(context.Request.IsHttps);
context.Features.Get<IHttpResetFeature>().Reset(0x010b); // H3_REQUEST_REJECTED
return Task.CompletedTask;
}
catch (Exception ex)
{
return context.Response.WriteAsync(ex.ToString());
}
}
private TaskCompletionSource _http3_ResetAfterHeadersCts = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
public async Task Http3_ResetAfterHeaders(HttpContext context)
{
try
{
Assert.True(context.Request.IsHttps);
await context.Response.Body.FlushAsync();
await _http3_ResetAfterHeadersCts.Task;
context.Features.Get<IHttpResetFeature>().Reset(0x010c); // H3_REQUEST_CANCELLED
}
catch (Exception ex)
{
await context.Response.WriteAsync(ex.ToString());
}
}
public Task Http3_ResetAfterHeaders_SetResult(HttpContext context)
{
_http3_ResetAfterHeadersCts.SetResult();
return Task.CompletedTask;
}
private TaskCompletionSource _http3_AppExceptionAfterHeaders_InternalErrorCts = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
public async Task Http3_AppExceptionAfterHeaders_InternalError(HttpContext context)
{
await context.Response.Body.FlushAsync();
await _http3_AppExceptionAfterHeaders_InternalErrorCts.Task;
throw new Exception("App Exception");
}
public Task Http3_AppExceptionAfterHeaders_InternalError_SetResult(HttpContext context)
{
_http3_AppExceptionAfterHeaders_InternalErrorCts.SetResult();
return Task.CompletedTask;
}
public Task Http3_Abort_Cancel(HttpContext context)
{
context.Abort();
return Task.CompletedTask;
}
internal static readonly HashSet<(string, StringValues, StringValues)> NullTrailers = new HashSet<(string, StringValues, StringValues)>()
{
("NullString", (string)null, (string)null),
("EmptyString", "", ""),
("NullStringArray", new string[] { null }, ""),
("EmptyStringArray", new string[] { "" }, ""),
("MixedStringArray", new string[] { null, "" }, new string[] { "", "" }),
("WithValidStrings", new string[] { null, "Value", "" }, new string[] { "", "Value", "" })
};
// https://tools.ietf.org/html/rfc7230#section-4.1.2
internal static readonly HashSet<string> DisallowedTrailers = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
// Message framing headers.
HeaderNames.TransferEncoding, HeaderNames.ContentLength,
// Routing headers.
HeaderNames.Host,
// Request modifiers: controls and conditionals.
// rfc7231#section-5.1: Controls.
HeaderNames.CacheControl, HeaderNames.Expect, HeaderNames.MaxForwards, HeaderNames.Pragma, HeaderNames.Range, HeaderNames.TE,
// rfc7231#section-5.2: Conditionals.
HeaderNames.IfMatch, HeaderNames.IfNoneMatch, HeaderNames.IfModifiedSince, HeaderNames.IfUnmodifiedSince, HeaderNames.IfRange,
// Authentication headers.
HeaderNames.WWWAuthenticate, HeaderNames.Authorization, HeaderNames.ProxyAuthenticate, HeaderNames.ProxyAuthorization, HeaderNames.SetCookie, HeaderNames.Cookie,
// Response control data.
// rfc7231#section-7.1: Control Data.
HeaderNames.Age, HeaderNames.Expires, HeaderNames.Date, HeaderNames.Location, HeaderNames.RetryAfter, HeaderNames.Vary, HeaderNames.Warning,
// Content-Encoding, Content-Type, Content-Range, and Trailer itself.
HeaderNames.ContentEncoding, HeaderNames.ContentType, HeaderNames.ContentRange, HeaderNames.Trailer
};
#endif
}
|