// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.IISIntegration.FunctionalTests; using Microsoft.AspNetCore.Server.IIS; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Primitives; using Xunit; namespace TestSite { public partial class Startup { public void Configure(IApplicationBuilder app) { TestStartup.Register(app, this); } private async Task ServerVariable(HttpContext ctx) { var varName = ctx.Request.Query["q"]; var newValue = ctx.Request.Query["v"]; var feature = ctx.Features.Get(); 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 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().Headers = headers; Assert.Same(headers, ctx.Features.Get().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().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().ReasonPhrase); ctx.Features.Get().ReasonPhrase = "Set Response"; Assert.Equal("Set Response", ctx.Features.Get().ReasonPhrase); Assert.NotNull(ctx.Response.Headers); var headers = new HeaderDictionary(); ctx.Features.Get().Headers = headers; Assert.Same(headers, ctx.Features.Get().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(); feature.ReasonPhrase = ctx.Request.Query["reason"]; feature.StatusCode = int.Parse(ctx.Request.Query["code"]); 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")) { await ctx.Response.WriteAsync(ctx.Request.Path.Value); return; } if (ctx.Request.Path.Value.StartsWith("/Query")) { 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)); } } 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 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 CheckEnvironmentVariable(HttpContext ctx) { var variable = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_TESTING_VALUE"); await ctx.Response.WriteAsync(variable); } private async Task CheckEnvironmentLongValueVariable(HttpContext ctx) { var variable = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_TESTING_LONG_VALUE"); await ctx.Response.WriteAsync(variable); } private async Task CheckAppendedEnvironmentVariable(HttpContext ctx) { var variable = Environment.GetEnvironmentVariable("ProgramFiles"); await ctx.Response.WriteAsync(variable); } private async Task CheckRemoveAuthEnvironmentVariable(HttpContext ctx) { var variable = Environment.GetEnvironmentVariable("ASPNETCORE_IIS_HTTPAUTH"); await ctx.Response.WriteAsync(variable); } 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) { 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 WriteManyTimesToResponseBody(HttpContext ctx) { for (var i = 0; i < 10000; i++) { await ctx.Response.WriteAsync("hello world"); } } private async Task ReadAndWriteEcho(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)); result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length); } } private async Task ReadAndFlushEcho(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(); result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length); } } private async Task ReadAndWriteEchoLines(HttpContext ctx) { 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 (!reader.EndOfStream) { var line = await reader.ReadLineAsync(); if (line == "") { return; } await ctx.Response.WriteAsync(line + Environment.NewLine); await ctx.Response.Body.FlushAsync(); } } private async Task ReadAndWriteEchoLinesNoBuffering(HttpContext ctx) { var feature = ctx.Features.Get(); feature.DisableResponseBuffering(); 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 (!reader.EndOfStream) { var line = await reader.ReadLineAsync(); if (line == "") { return; } await ctx.Response.WriteAsync(line + Environment.NewLine); } } private async Task ReadPartialBody(HttpContext ctx) { 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) { await ctx.Request.Body.CopyToAsync(ctx.Response.Body); } private async Task UpgradeFeatureDetection(HttpContext ctx) { if (ctx.Features.Get() != null) { await ctx.Response.WriteAsync("Enabled"); } else { await ctx.Response.WriteAsync("Disabled"); } } private async Task TestReadOffsetWorks(HttpContext ctx) { var buffer = new byte[11]; ctx.Request.Body.Read(buffer, 0, 6); ctx.Request.Body.Read(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 = 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 async Task Shutdown(HttpContext ctx) { await ctx.Response.WriteAsync("Shutting down"); ctx.RequestServices.GetService().StopApplication(); } 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(); 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"]); } }