From 509ed7ae74de659dc5a8d38696ae103b70ba82a1 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Fri, 14 May 2021 00:45:53 +0200 Subject: [PATCH] Refresh network tests server code to Kestrel (#52642) * copy from https://github.com/davidsh/corefx-net-endpoint/commit/999f8356816be739ec65dae5a1a89782b3936a66 * license headers * missing md5 test * issue for missing functionality --- .../Net/Prerequisites/NetCoreServer.sln | 25 +++ .../NetCoreServer/GenericHandler.cs | 104 +++++++++++ .../NetCoreServer/Handlers/DeflateHandler.cs | 25 +++ .../NetCoreServer/Handlers/EchoHandler.cs | 44 +++++ .../Handlers/EchoWebSocketHandler.cs | 163 ++++++++++++++++++ .../Handlers/EchoWebSocketHeadersHandler.cs | 83 +++++++++ .../Handlers/EmptyContentHandler.cs | 16 ++ .../NetCoreServer/Handlers/GZipHandler.cs | 25 +++ .../NetCoreServer/Handlers/RedirectHandler.cs | 77 +++++++++ .../Handlers/StatusCodeHandler.cs | 29 ++++ .../NetCoreServer/Handlers/TestHandler.cs | 38 ++++ .../Handlers/VerifyUploadHandler.cs | 86 +++++++++ .../NetCoreServer/Handlers/VersionHandler.cs | 44 +++++ .../Helpers/AuthenticationHelper.cs | 115 ++++++++++++ .../NetCoreServer/Helpers/ContentHelper.cs | 54 ++++++ .../Helpers/NameValueCollectionConverter.cs | 76 ++++++++ .../NetCoreServer/Helpers/RequestHelper.cs | 38 ++++ .../Helpers/RequestInformation.cs | 102 +++++++++++ .../NetCoreServer/NetCoreServer.csproj | 35 ++++ .../Prerequisites/NetCoreServer/Program.cs | 23 +++ .../Prerequisites/NetCoreServer/Startup.cs | 23 +++ .../appsettings.Development.json | 9 + .../NetCoreServer/appsettings.json | 8 + 23 files changed, 1242 insertions(+) create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer.sln create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/GenericHandler.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/DeflateHandler.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoHandler.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHeadersHandler.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EmptyContentHandler.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/GZipHandler.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/RedirectHandler.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/StatusCodeHandler.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/TestHandler.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/VerifyUploadHandler.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/VersionHandler.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/AuthenticationHelper.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/ContentHelper.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/NameValueCollectionConverter.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/RequestHelper.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/RequestInformation.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/NetCoreServer.csproj create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Program.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Startup.cs create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/appsettings.Development.json create mode 100644 src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/appsettings.json diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer.sln b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer.sln new file mode 100644 index 00000000000000..8e5d44de5cd87c --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.438 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetCoreServer", "NetCoreServer\NetCoreServer.csproj", "{86E9A13D-9F4A-45DE-B0BB-CBB6A6533867}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {86E9A13D-9F4A-45DE-B0BB-CBB6A6533867}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86E9A13D-9F4A-45DE-B0BB-CBB6A6533867}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86E9A13D-9F4A-45DE-B0BB-CBB6A6533867}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86E9A13D-9F4A-45DE-B0BB-CBB6A6533867}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2F9A0637-452E-4FB9-9403-CB52944982DA} + EndGlobalSection +EndGlobal diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/GenericHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/GenericHandler.cs new file mode 100644 index 00000000000000..6efc71033a308d --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/GenericHandler.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; + +namespace NetCoreServer +{ + public class GenericHandler + { + // Must have constructor with this signature, otherwise exception at run time. + public GenericHandler(RequestDelegate next) + { + // This is an HTTP Handler, so no need to store next. + } + + public async Task Invoke(HttpContext context) + { + PathString path = context.Request.Path; + if (path.Equals(new PathString("/deflate.ashx"))) + { + await DeflateHandler.InvokeAsync(context); + return; + } + + if (path.Equals(new PathString("/echo.ashx"))) + { + await EchoHandler.InvokeAsync(context); + return; + } + + if (path.Equals(new PathString("/emptycontent.ashx"))) + { + EmptyContentHandler.Invoke(context); + return; + } + + if (path.Equals(new PathString("/gzip.ashx"))) + { + await GZipHandler.InvokeAsync(context); + return; + } + + if (path.Equals(new PathString("/redirect.ashx"))) + { + RedirectHandler.Invoke(context); + return; + } + + if (path.Equals(new PathString("/statuscode.ashx"))) + { + StatusCodeHandler.Invoke(context); + return; + } + + if (path.Equals(new PathString("/verifyupload.ashx"))) + { + await VerifyUploadHandler.InvokeAsync(context); + return; + } + + if (path.Equals(new PathString("/version"))) + { + await VersionHandler.InvokeAsync(context); + return; + } + + if (path.Equals(new PathString("/websocket/echowebsocket.ashx"))) + { + await EchoWebSocketHandler.InvokeAsync(context); + return; + } + + if (path.Equals(new PathString("/websocket/echowebsocketheaders.ashx"))) + { + await EchoWebSocketHeadersHandler.InvokeAsync(context); + return; + } + if (path.Equals(new PathString("/test.ashx"))) + { + await TestHandler.InvokeAsync(context); + return; + } + + // Default handling. + await EchoHandler.InvokeAsync(context); + } + } + + public static class GenericHandlerExtensions + { + public static IApplicationBuilder UseGenericHandler(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + + public static void SetStatusDescription(this HttpResponse response, string description) + { + response.HttpContext.Features.Get().ReasonPhrase = description; + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/DeflateHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/DeflateHandler.cs new file mode 100644 index 00000000000000..c4c6d267e807fd --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/DeflateHandler.cs @@ -0,0 +1,25 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace NetCoreServer +{ + public class DeflateHandler + { + public static async Task InvokeAsync(HttpContext context) + { + string responseBody = "Sending DEFLATE compressed"; + + context.Response.Headers.Add("Content-MD5", Convert.ToBase64String(ContentHelper.ComputeMD5Hash(responseBody))); + context.Response.Headers.Add("Content-Encoding", "deflate"); + + context.Response.ContentType = "text/plain"; + + byte[] bytes = ContentHelper.GetDeflateBytes(responseBody); + await context.Response.Body.WriteAsync(bytes); + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoHandler.cs new file mode 100644 index 00000000000000..ca6fc8ebd7e605 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoHandler.cs @@ -0,0 +1,44 @@ +// 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.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace NetCoreServer +{ + public class EchoHandler + { + public static async Task InvokeAsync(HttpContext context) + { + RequestHelper.AddResponseCookies(context); + + if (!AuthenticationHelper.HandleAuthentication(context)) + { + return; + } + + // Add original request method verb as a custom response header. + context.Response.Headers.Add("X-HttpRequest-Method", context.Request.Method); + + // Echo back JSON encoded payload. + RequestInformation info = await RequestInformation.CreateAsync(context.Request); + string echoJson = info.SerializeToJson(); + + // Compute MD5 hash so that clients can verify the received data. + using (MD5 md5 = MD5.Create()) + { + byte[] bytes = Encoding.UTF8.GetBytes(echoJson); + byte[] hash = md5.ComputeHash(bytes); + string encodedHash = Convert.ToBase64String(hash); + + context.Response.Headers.Add("Content-MD5", encodedHash); + context.Response.ContentType = "application/json"; + context.Response.ContentLength = bytes.Length; + await context.Response.Body.WriteAsync(bytes, 0, bytes.Length); + } + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs new file mode 100644 index 00000000000000..21ee6346eff8e3 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHandler.cs @@ -0,0 +1,163 @@ +// 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.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace NetCoreServer +{ + public class EchoWebSocketHandler + { + private const int MaxBufferSize = 128 * 1024; + + public static async Task InvokeAsync(HttpContext context) + { + QueryString queryString = context.Request.QueryString; + bool replyWithPartialMessages = queryString.HasValue && queryString.Value.Contains("replyWithPartialMessages"); + bool replyWithEnhancedCloseMessage = queryString.HasValue && queryString.Value.Contains("replyWithEnhancedCloseMessage"); + + string subProtocol = context.Request.Query["subprotocol"]; + + if (context.Request.QueryString.HasValue && context.Request.QueryString.Value.Contains("delay10sec")) + { + Thread.Sleep(10000); + } + + try + { + if (!context.WebSockets.IsWebSocketRequest) + { + context.Response.StatusCode = 200; + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync("Not a websocket request"); + + return; + } + + WebSocket socket; + if (!string.IsNullOrEmpty(subProtocol)) + { + socket = await context.WebSockets.AcceptWebSocketAsync(subProtocol); + } + else + { + socket = await context.WebSockets.AcceptWebSocketAsync(); + } + + await ProcessWebSocketRequest(socket, replyWithPartialMessages, replyWithEnhancedCloseMessage); + } + catch (Exception) + { + // We might want to log these exceptions. But for now we ignore them. + } + } + + private static async Task ProcessWebSocketRequest( + WebSocket socket, + bool replyWithPartialMessages, + bool replyWithEnhancedCloseMessage) + { + var receiveBuffer = new byte[MaxBufferSize]; + var throwAwayBuffer = new byte[MaxBufferSize]; + + // Stay in loop while websocket is open + while (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseSent) + { + var receiveResult = await socket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); + if (receiveResult.MessageType == WebSocketMessageType.Close) + { + if (receiveResult.CloseStatus == WebSocketCloseStatus.Empty) + { + await socket.CloseAsync(WebSocketCloseStatus.Empty, null, CancellationToken.None); + } + else + { + WebSocketCloseStatus closeStatus = receiveResult.CloseStatus.GetValueOrDefault(); + await socket.CloseAsync( + closeStatus, + replyWithEnhancedCloseMessage ? + $"Server received: {(int)closeStatus} {receiveResult.CloseStatusDescription}" : + receiveResult.CloseStatusDescription, + CancellationToken.None); + } + + continue; + } + + // Keep reading until we get an entire message. + int offset = receiveResult.Count; + while (receiveResult.EndOfMessage == false) + { + if (offset < MaxBufferSize) + { + receiveResult = await socket.ReceiveAsync( + new ArraySegment(receiveBuffer, offset, MaxBufferSize - offset), + CancellationToken.None); + } + else + { + receiveResult = await socket.ReceiveAsync( + new ArraySegment(throwAwayBuffer), + CancellationToken.None); + } + + offset += receiveResult.Count; + } + + // Close socket if the message was too big. + if (offset > MaxBufferSize) + { + await socket.CloseAsync( + WebSocketCloseStatus.MessageTooBig, + String.Format("{0}: {1} > {2}", WebSocketCloseStatus.MessageTooBig.ToString(), offset, MaxBufferSize), + CancellationToken.None); + + continue; + } + + bool sendMessage = false; + if (receiveResult.MessageType == WebSocketMessageType.Text) + { + string receivedMessage = Encoding.UTF8.GetString(receiveBuffer, 0, offset); + if (receivedMessage == ".close") + { + await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, receivedMessage, CancellationToken.None); + } + if (receivedMessage == ".shutdown") + { + await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, receivedMessage, CancellationToken.None); + } + else if (receivedMessage == ".abort") + { + socket.Abort(); + } + else if (receivedMessage == ".delay5sec") + { + await Task.Delay(5000); + } + else if (socket.State == WebSocketState.Open) + { + sendMessage = true; + } + } + else + { + sendMessage = true; + } + + if (sendMessage) + { + await socket.SendAsync( + new ArraySegment(receiveBuffer, 0, offset), + receiveResult.MessageType, + !replyWithPartialMessages, + CancellationToken.None); + } + } + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHeadersHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHeadersHandler.cs new file mode 100644 index 00000000000000..c5ebca53b63a94 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoWebSocketHeadersHandler.cs @@ -0,0 +1,83 @@ +// 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.Collections.Generic; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; + +namespace NetCoreServer +{ + public class EchoWebSocketHeadersHandler + { + private const int MaxBufferSize = 1024; + + public static async Task InvokeAsync(HttpContext context) + { + try + { + if (!context.WebSockets.IsWebSocketRequest) + { + context.Response.StatusCode = 200; + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync("Not a websocket request"); + + return; + } + + WebSocket socket = await context.WebSockets.AcceptWebSocketAsync(); + await ProcessWebSocketRequest(socket, context.Request.Headers); + + } + catch (Exception) + { + // We might want to log these exceptions. But for now we ignore them. + } + } + + private static async Task ProcessWebSocketRequest(WebSocket socket, IHeaderDictionary headers) + { + var receiveBuffer = new byte[MaxBufferSize]; + + // Reflect all headers and cookies + var sb = new StringBuilder(); + sb.AppendLine("Headers:"); + + foreach (KeyValuePair pair in headers) + { + sb.Append(pair.Key); + sb.Append(":"); + sb.AppendLine(pair.Value.ToString()); + } + + byte[] sendBuffer = Encoding.UTF8.GetBytes(sb.ToString()); + await socket.SendAsync(new ArraySegment(sendBuffer), WebSocketMessageType.Text, true, new CancellationToken()); + + // Stay in loop while websocket is open + while (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseSent) + { + var receiveResult = await socket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); + if (receiveResult.MessageType == WebSocketMessageType.Close) + { + if (receiveResult.CloseStatus == WebSocketCloseStatus.Empty) + { + await socket.CloseAsync(WebSocketCloseStatus.Empty, null, CancellationToken.None); + } + else + { + await socket.CloseAsync( + receiveResult.CloseStatus.GetValueOrDefault(), + receiveResult.CloseStatusDescription, + CancellationToken.None); + } + + continue; + } + } + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EmptyContentHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EmptyContentHandler.cs new file mode 100644 index 00000000000000..1a711c9be7408a --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EmptyContentHandler.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Http; + +namespace NetCoreServer +{ + public class EmptyContentHandler + { + public static void Invoke(HttpContext context) + { + // By default, this empty method sends back a 200 status code with 'Content-Length: 0' response header. + // There are no other entity-body related (i.e. 'Content-Type') headers returned. + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/GZipHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/GZipHandler.cs new file mode 100644 index 00000000000000..3c8c4542545089 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/GZipHandler.cs @@ -0,0 +1,25 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace NetCoreServer +{ + public class GZipHandler + { + public static async Task InvokeAsync(HttpContext context) + { + string responseBody = "Sending GZIP compressed"; + + context.Response.Headers.Add("Content-MD5", Convert.ToBase64String(ContentHelper.ComputeMD5Hash(responseBody))); + context.Response.Headers.Add("Content-Encoding", "gzip"); + + context.Response.ContentType = "text/plain"; + + byte[] bytes = ContentHelper.GetGZipBytes(responseBody); + await context.Response.Body.WriteAsync(bytes); + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/RedirectHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/RedirectHandler.cs new file mode 100644 index 00000000000000..cb7ce16290771f --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/RedirectHandler.cs @@ -0,0 +1,77 @@ +// 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 Microsoft.AspNetCore.Http; + +namespace NetCoreServer +{ + public class RedirectHandler + { + public static void Invoke(HttpContext context) + { + int statusCode = 302; + string statusCodeString = context.Request.Query["statuscode"]; + if (!string.IsNullOrEmpty(statusCodeString)) + { + try + { + statusCode = int.Parse(statusCodeString); + if (statusCode < 300 || statusCode > 308) + { + context.Response.StatusCode = 400; + context.Response.SetStatusDescription($"Invalid redirect statuscode: {statusCodeString}"); + return; + } + } + catch (Exception) + { + context.Response.StatusCode = 400; + context.Response.SetStatusDescription($"Error parsing statuscode: {statusCodeString}"); + return; + } + } + + string redirectUri = context.Request.Query["uri"]; + if (string.IsNullOrEmpty(redirectUri)) + { + context.Response.StatusCode = 400; + context.Response.SetStatusDescription($"Missing redirection uri"); + return; + } + + string hopsString = context.Request.Query["hops"]; + int hops = 1; + if (!string.IsNullOrEmpty(hopsString)) + { + try + { + hops = int.Parse(hopsString); + } + catch (Exception) + { + context.Response.StatusCode = 400; + context.Response.SetStatusDescription($"Error parsing hops: {hopsString}"); + return; + } + } + + RequestHelper.AddResponseCookies(context); + + if (hops <= 1) + { + context.Response.Headers.Add("Location", redirectUri); + } + else + { + context.Response.Headers.Add( + "Location", + string.Format("/Redirect.ashx?uri={0}&hops={1}", + redirectUri, + hops - 1)); + } + + context.Response.StatusCode = statusCode; + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/StatusCodeHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/StatusCodeHandler.cs new file mode 100644 index 00000000000000..ae039a3c38f731 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/StatusCodeHandler.cs @@ -0,0 +1,29 @@ +// 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 Microsoft.AspNetCore.Http; + +namespace NetCoreServer +{ + public class StatusCodeHandler + { + public static void Invoke(HttpContext context) + { + string statusCodeString = context.Request.Query["statuscode"]; + string statusDescription = context.Request.Query["statusdescription"]; + try + { + int statusCode = int.Parse(statusCodeString); + context.Response.StatusCode = statusCode; + context.Response.SetStatusDescription( + string.IsNullOrWhiteSpace(statusDescription) ? " " : statusDescription); + } + catch (Exception) + { + context.Response.StatusCode = 400; + context.Response.SetStatusDescription($"Error parsing statuscode: {statusCodeString}"); + } + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/TestHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/TestHandler.cs new file mode 100644 index 00000000000000..936832d3babde5 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/TestHandler.cs @@ -0,0 +1,38 @@ +// 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.Collections.Generic; +using System.Collections.Specialized; +using System.Net; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; + +namespace NetCoreServer +{ + public class TestHandler + { + public static async Task InvokeAsync(HttpContext context) + { + RequestInformation info = await RequestInformation.CreateAsync(context.Request); + + string echoJson = info.SerializeToJson(); + + // Compute MD5 hash to clients can verify the received data. + MD5 md5 = MD5.Create(); + byte[] bytes = Encoding.ASCII.GetBytes(echoJson); + var hash = md5.ComputeHash(bytes); + string encodedHash = Convert.ToBase64String(hash); + context.Response.Headers.Add("Content-MD5", encodedHash); + + RequestInformation newEcho = RequestInformation.DeSerializeFromJson(echoJson); + context.Response.ContentType = "text/plain"; //"application/json"; + await context.Response.WriteAsync(echoJson); + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/VerifyUploadHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/VerifyUploadHandler.cs new file mode 100644 index 00000000000000..69d3190d35be0a --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/VerifyUploadHandler.cs @@ -0,0 +1,86 @@ +// 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.IO; +using System.Security.Cryptography; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace NetCoreServer +{ + public class VerifyUploadHandler + { + public static async Task InvokeAsync(HttpContext context) + { + // Report back original request method verb. + context.Response.Headers.Add("X-HttpRequest-Method", context.Request.Method); + + // Report back original entity-body related request headers. + string contentLength = context.Request.Headers["Content-Length"]; + if (!string.IsNullOrEmpty(contentLength)) + { + context.Response.Headers.Add("X-HttpRequest-Headers-ContentLength", contentLength); + } + + string transferEncoding = context.Request.Headers["Transfer-Encoding"]; + if (!string.IsNullOrEmpty(transferEncoding)) + { + context.Response.Headers.Add("X-HttpRequest-Headers-TransferEncoding", transferEncoding); + } + + // Get request body. + byte[] requestBodyBytes = await ReadAllRequestBytesAsync(context); + + // Check MD5 checksum for non-empty request body. + if (requestBodyBytes.Length > 0) + { + // Get expected MD5 hash of request body. + string expectedHash = context.Request.Headers["Content-MD5"]; + if (string.IsNullOrEmpty(expectedHash)) + { + context.Response.StatusCode = 400; + context.Response.SetStatusDescription("Missing 'Content-MD5' request header"); + return; + } + + // Compute MD5 hash of received request body. + string actualHash; + using (MD5 md5 = MD5.Create()) + { + byte[] hash = md5.ComputeHash(requestBodyBytes); + actualHash = Convert.ToBase64String(hash); + } + + if (expectedHash == actualHash) + { + context.Response.StatusCode = 200; + } + else + { + context.Response.StatusCode = 400; + context.Response.SetStatusDescription("Received request body fails MD5 checksum"); + } + } + else + { + context.Response.StatusCode = 200; + } + } + + private static async Task ReadAllRequestBytesAsync(HttpContext context) + { + Stream requestStream = context.Request.Body; + byte[] buffer = new byte[16 * 1024]; + using (MemoryStream ms = new MemoryStream()) + { + int read; + while ((read = await requestStream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + ms.Write(buffer, 0, read); + } + return ms.ToArray(); + } + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/VersionHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/VersionHandler.cs new file mode 100644 index 00000000000000..2cffa413a75978 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/VersionHandler.cs @@ -0,0 +1,44 @@ +// 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.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace NetCoreServer +{ + public class VersionHandler + { + public static async Task InvokeAsync(HttpContext context) + { + string versionInfo = GetVersionInfo(); + byte[] bytes = Encoding.UTF8.GetBytes(versionInfo); + + context.Response.ContentType = "text/plain"; + context.Response.ContentLength = bytes.Length; + await context.Response.Body.WriteAsync(bytes, 0, bytes.Length); + } + + private static string GetVersionInfo() + { + Type t = typeof(VersionHandler); + string path = t.Assembly.Location; + FileVersionInfo fi = FileVersionInfo.GetVersionInfo(path); + + var buffer = new StringBuilder(); + buffer.AppendLine($"Information for: {Path.GetFileName(path)}"); + buffer.AppendLine($"Location: {Path.GetDirectoryName(path)}"); + buffer.AppendLine($"Framework: {RuntimeInformation.FrameworkDescription}"); + buffer.AppendLine($"File Version: {fi.FileVersion}"); + buffer.AppendLine($"Product Version: {fi.ProductVersion}"); + buffer.AppendLine($"Creation Date: {File.GetCreationTime(path)}"); + buffer.AppendLine($"Last Modified: {File.GetLastWriteTime(path)}"); + + return buffer.ToString(); + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/AuthenticationHelper.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/AuthenticationHelper.cs new file mode 100644 index 00000000000000..c0d0d2e1dee691 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/AuthenticationHelper.cs @@ -0,0 +1,115 @@ +// 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.Text; +using Microsoft.AspNetCore.Http; + +namespace NetCoreServer +{ + public class AuthenticationHelper + { + public static bool HandleAuthentication(HttpContext context) + { + string authType = context.Request.Query["auth"]; + string user = context.Request.Query["user"]; + string password = context.Request.Query["password"]; + string domain = context.Request.Query["domain"]; + + if (string.Equals("basic", authType, StringComparison.OrdinalIgnoreCase)) + { + if (!HandleBasicAuthentication(context, user, password, domain)) + { + return false; + } + } + else if (string.Equals("Negotiate", authType, StringComparison.OrdinalIgnoreCase) || + string.Equals("NTLM", authType, StringComparison.OrdinalIgnoreCase)) + { + if (!HandleChallengeResponseAuthentication(context, authType, user, password, domain)) + { + return false; + } + } + else if (authType != null) + { + context.Response.StatusCode = 501; + context.Response.SetStatusDescription($"Unsupported auth type: {authType}"); + return false; + } + + return true; + } + + private static bool HandleBasicAuthentication(HttpContext context, string user, string password, string domain) + { + const string WwwAuthenticateHeaderValue = "Basic realm=\"corefx-networking\""; + + string authHeader = context.Request.Headers["Authorization"]; + if (authHeader == null) + { + context.Response.StatusCode = 401; + context.Response.Headers.Add("WWW-Authenticate", WwwAuthenticateHeaderValue); + return false; + } + + string[] split = authHeader.Split(new Char[] { ' ' }); + if (split.Length < 2) + { + context.Response.StatusCode = 500; + context.Response.SetStatusDescription($"Invalid Authorization header: {authHeader}"); + return false; + } + + if (!string.Equals("basic", split[0], StringComparison.OrdinalIgnoreCase)) + { + context.Response.StatusCode = 500; + context.Response.SetStatusDescription($"Unsupported auth type: {split[0]}"); + return false; + } + + // Decode base64 username:password. + byte[] bytes = Convert.FromBase64String(split[1]); + string credential = Encoding.ASCII.GetString(bytes); + string[] pair = credential.Split(new Char[] { ':' }); + + // Prefix "domain\" to username if domain is specified. + if (domain != null) + { + user = domain + "\\" + user; + } + + if (pair.Length != 2 || pair[0] != user || pair[1] != password) + { + context.Response.StatusCode = 401; + context.Response.Headers.Add("WWW-Authenticate", WwwAuthenticateHeaderValue); + return false; + } + + // Success. + return true; + } + private static bool HandleChallengeResponseAuthentication( + HttpContext context, + string authType, + string user, + string password, + string domain) + { + string authHeader = context.Request.Headers["Authorization"]; + if (authHeader == null) + { + context.Response.StatusCode = 401; + context.Response.Headers.Add("WWW-Authenticate", authType); + return false; + } + + // We don't fully support this authentication method. + context.Response.StatusCode = 501; + context.Response.SetStatusDescription( + $"Attempt to use unsupported challenge/response auth type. {authType}: {authHeader}"); + + return false; + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/ContentHelper.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/ContentHelper.cs new file mode 100644 index 00000000000000..696a7632d28fd7 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/ContentHelper.cs @@ -0,0 +1,54 @@ +// 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.IO; +using System.IO.Compression; +using System.Security.Cryptography; +using System.Text; + +namespace NetCoreServer +{ + public class ContentHelper + { + public static byte[] GetDeflateBytes(string data) + { + byte[] bytes = Encoding.UTF8.GetBytes(data); + var compressedStream = new MemoryStream(); + + using (var compressor = new DeflateStream(compressedStream, CompressionMode.Compress, true)) + { + compressor.Write(bytes, 0, bytes.Length); + } + + return compressedStream.ToArray(); + } + + public static byte[] GetGZipBytes(string data) + { + byte[] bytes = Encoding.UTF8.GetBytes(data); + var compressedStream = new MemoryStream(); + + using (var compressor = new GZipStream(compressedStream, CompressionMode.Compress, true)) + { + compressor.Write(bytes, 0, bytes.Length); + } + + return compressedStream.ToArray(); + + } + + public static byte[] ComputeMD5Hash(string data) + { + return ComputeMD5Hash(Encoding.UTF8.GetBytes(data)); + } + + public static byte[] ComputeMD5Hash(byte[] data) + { + using (MD5 md5 = MD5.Create()) + { + return md5.ComputeHash(data); + } + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/NameValueCollectionConverter.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/NameValueCollectionConverter.cs new file mode 100644 index 00000000000000..814ddb9012caaa --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/NameValueCollectionConverter.cs @@ -0,0 +1,76 @@ +// 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.Collections.Specialized; + +using Newtonsoft.Json; + +namespace NetCoreServer +{ + public class NameValueCollectionConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var collection = value as NameValueCollection; + if (collection == null) + { + return; + } + + writer.Formatting = Formatting.Indented; + writer.WriteStartObject(); + foreach (var key in collection.AllKeys) + { + writer.WritePropertyName(key); + writer.WriteValue(collection.Get(key)); + } + writer.WriteEndObject(); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var nameValueCollection = new NameValueCollection(); + var key = ""; + while (reader.Read()) + { + if (reader.TokenType == JsonToken.StartObject) + { + nameValueCollection = new NameValueCollection(); + } + if (reader.TokenType == JsonToken.EndObject) + { + return nameValueCollection; + } + if (reader.TokenType == JsonToken.PropertyName) + { + key = reader.Value.ToString(); + } + if (reader.TokenType == JsonToken.String) + nameValueCollection.Add(key, reader.Value.ToString()); + } + return nameValueCollection; + } + + public override bool CanConvert(Type objectType) + { + return IsTypeDerivedFromType(objectType, typeof(NameValueCollection)); + } + + private bool IsTypeDerivedFromType(Type childType, Type parentType) + { + Type testType = childType; + while (testType != null) + { + if (testType == parentType) + { + return true; + } + + testType = testType.BaseType; + } + + return false; + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/RequestHelper.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/RequestHelper.cs new file mode 100644 index 00000000000000..b72c1763ff0f86 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/RequestHelper.cs @@ -0,0 +1,38 @@ +// 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.Collections.Generic; +using System.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; + +namespace NetCoreServer +{ + public class RequestHelper + { + public static void AddResponseCookies(HttpContext context) + { + // Turn all 'X-SetCookie' request headers into 'Set-Cookie' response headers. + foreach (KeyValuePair pair in context.Request.Headers) + { + if (string.Equals(pair.Key, "X-SetCookie", StringComparison.OrdinalIgnoreCase)) + { + context.Response.Headers.Add("Set-Cookie", pair.Value.ToString()); + } + } + } + + public static CookieCollection GetRequestCookies(HttpRequest request) + { + var cookieCollection = new CookieCollection(); + foreach (KeyValuePair pair in request.Cookies) + { + var cookie = new Cookie(pair.Key, pair.Value); + cookieCollection.Add(cookie); + } + + return cookieCollection; + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/RequestInformation.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/RequestInformation.cs new file mode 100644 index 00000000000000..fe5bb30eaaa2f0 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Helpers/RequestInformation.cs @@ -0,0 +1,102 @@ +// 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.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using Newtonsoft.Json; + +namespace NetCoreServer +{ + public class RequestInformation + { + public string Method { get; private set; } + + public string Url { get; private set; } + + public NameValueCollection Headers { get; private set; } + + public NameValueCollection Cookies { get; private set; } + + public string BodyContent { get; private set; } + + public int BodyLength { get; private set; } + + public bool SecureConnection { get; private set; } + + public bool ClientCertificatePresent { get; private set; } + + public X509Certificate2 ClientCertificate { get; private set; } + + public static async Task CreateAsync(HttpRequest request) + { + var info = new RequestInformation(); + info.Method = request.Method; + info.Url = request.Path + request.QueryString; + info.Headers = new NameValueCollection(); + foreach (KeyValuePair header in request.Headers) + { + info.Headers.Add(header.Key, header.Value.ToString()); + } + + var cookies = new NameValueCollection(); + CookieCollection cookieCollection = RequestHelper.GetRequestCookies(request); + foreach (Cookie cookie in cookieCollection) + { + cookies.Add(cookie.Name, cookie.Value); + } + info.Cookies = cookies; + + string body = string.Empty; + try + { + Stream stream = request.Body; + using (var reader = new StreamReader(stream)) + { + body = await reader.ReadToEndAsync(); + } + } + catch (Exception ex) + { + // We might want to log these exceptions also. + body = ex.ToString(); + } + finally + { + info.BodyContent = body; + info.BodyLength = body.Length; + } + + info.SecureConnection = request.IsHttps; + + // FixMe: https://github.com/dotnet/runtime/issues/52693 + // info.ClientCertificate = request.ClientCertificate; + + return info; + } + + public static RequestInformation DeSerializeFromJson(string json) + { + return (RequestInformation)JsonConvert.DeserializeObject( + json, + typeof(RequestInformation), + new NameValueCollectionConverter()); + + } + + public string SerializeToJson() + { + return JsonConvert.SerializeObject(this, new NameValueCollectionConverter()); + } + + private RequestInformation() + { + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/NetCoreServer.csproj b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/NetCoreServer.csproj new file mode 100644 index 00000000000000..d9b272d4d32414 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/NetCoreServer.csproj @@ -0,0 +1,35 @@ + + + + netcoreapp3.1 + InProcess + Exe + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Program.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Program.cs new file mode 100644 index 00000000000000..4b58c7563f21f2 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Program.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace NetCoreServer +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Startup.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Startup.cs new file mode 100644 index 00000000000000..3ebe39a7d2c510 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Startup.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; + +namespace NetCoreServer +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseWebSockets(); + app.UseGenericHandler(); + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/appsettings.Development.json b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/appsettings.Development.json new file mode 100644 index 00000000000000..e203e9407e74a6 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/appsettings.json b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/appsettings.json new file mode 100644 index 00000000000000..def9159a7d9403 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +}