diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 00000000..f71b1eb2 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,2 @@ +paths-ignore: + - '**/bundle.js' diff --git a/.github/stale.yml b/.github/stale.yml index dc90e5a1..d302861c 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -5,7 +5,7 @@ daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - pinned - - security + - area:security # Label to use when marking an issue as stale staleLabel: wontfix # Comment to post when marking an issue as stale. Set to `false` to disable diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cfd90b60..6bc51722 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,14 +11,14 @@ jobs: os: [ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v2 with: - dotnet-version: 5.0 + dotnet-version: 6.0 - name: Test with dotnet run: dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov /p:Exclude=[NUnit3.TestAdapter]* ./test/EmbedIO.Tests/EmbedIO.Tests.csproj -c Release - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v3 with: file: test/EmbedIO.Tests/coverage.info token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..394c8f98 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,67 @@ +name: "CodeQL" + +on: + push: + branches: [master, "v[1-9].X"] + pull_request: + # The branches below must be a subset of the branches above + branches: [master, "v[1-9].X"] + schedule: + - cron: '0 6 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['csharp', 'javascript'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + config-file: ./.github/codeql/codeql-config.yml + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/Directory.Build.props b/Directory.Build.props index de4992f2..da2d3396 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,22 +5,22 @@ - Copyright (C) Unosquare 2013-2021 + Copyright (C) Unosquare 2013-2022 3.5.0 + true latest enable true $(MSBuildThisFileDirectory)StyleCop.Analyzers.ruleset + latest + All + true - - all - runtime; build; native; contentfiles; analyzers - all runtime; build; native; contentfiles; analyzers diff --git a/Directory.Packages.props b/Directory.Packages.props index d8ab33a6..5c6a8124 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,14 +1,13 @@ - - - + + - - - - + + + + diff --git a/EmbedIO.sln b/EmbedIO.sln index c883f1de..fecdce67 100644 --- a/EmbedIO.sln +++ b/EmbedIO.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29609.76 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32526.322 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{97BC259A-4E78-4BA8-8F4D-2656BC78BB34}" EndProject diff --git a/EmbedIO.sln.DotSettings b/EmbedIO.sln.DotSettings index ebc98fb9..d90d95cb 100644 --- a/EmbedIO.sln.DotSettings +++ b/EmbedIO.sln.DotSettings @@ -467,6 +467,7 @@ 2 True <Configurator><ConnectList /></Configurator> + True True True True diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 00000000..45bc4e94 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/README.md b/README.md index 1ceefdde..bbdc8a66 100644 --- a/README.md +++ b/README.md @@ -223,9 +223,9 @@ namespace Unosquare /// /// Defines a very simple chat server. /// - public class WebSocketsChatServer : WebSocketModule + public class WebSocketsChatModule : WebSocketModule { - public WebSocketsChatServer(string urlPath) + public WebSocketsChatModule(string urlPath) : base(urlPath, true) { // placeholder diff --git a/appveyor.yml b/appveyor.yml index fdc06ccd..c21d1018 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,5 @@ version: '3.0.{build}' -image: -- Visual Studio 2019 -- Ubuntu +image: ["Visual Studio 2022"] # TODO: change back to ["Visual Studio 2022", "Ubuntu"] when Ubuntu image gets .NET SDK updated to 6.0.300 environment: APPVEYOR_YML_DISABLE_PS_LINUX: true COVERALLS_REPO_TOKEN: diff --git a/global.json b/global.json new file mode 100644 index 00000000..3a8f1701 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "6.0.300", + "rollForward": "latestMinor", + "allowPrerelease": false + } +} diff --git a/src/EmbedIO.Samples/EmbedIO.Samples.csproj b/src/EmbedIO.Samples/EmbedIO.Samples.csproj index 468f8d64..ba3a79e6 100644 --- a/src/EmbedIO.Samples/EmbedIO.Samples.csproj +++ b/src/EmbedIO.Samples/EmbedIO.Samples.csproj @@ -1,7 +1,7 @@  - net5.0 + net6.0 EmbedIO.Samples Exe false diff --git a/src/EmbedIO/Files/Internal/HtmlDirectoryLister.cs b/src/EmbedIO/Files/Internal/HtmlDirectoryLister.cs index 7a8c4afc..f85eebf2 100644 --- a/src/EmbedIO/Files/Internal/HtmlDirectoryLister.cs +++ b/src/EmbedIO/Files/Internal/HtmlDirectoryLister.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Net; -using System.Text; using System.Threading; using System.Threading.Tasks; using EmbedIO.Utilities; @@ -52,7 +51,7 @@ public async Task ListDirectoryAsync( foreach (var directory in entries.Where(m => m.IsDirectory).OrderBy(e => e.Name)) { - text.Write($"{WebUtility.HtmlEncode(directory.Name)}"); + text.Write($"{WebUtility.HtmlEncode(directory.Name)}"); text.Write(new string(' ', Math.Max(1, MaxEntryLength - directory.Name.Length + 1))); text.Write(HttpDate.Format(directory.LastModifiedUtc)); text.Write('\n'); diff --git a/src/EmbedIO/Net/Internal/HttpListenerRequest.cs b/src/EmbedIO/Net/Internal/HttpListenerRequest.cs index 8b4f5597..548f44d7 100644 --- a/src/EmbedIO/Net/Internal/HttpListenerRequest.cs +++ b/src/EmbedIO/Net/Internal/HttpListenerRequest.cs @@ -218,10 +218,11 @@ internal void FinishInitialization() host = rawUri?.Host ?? UserHostAddress; } - var colon = host.LastIndexOf(':'); - if (colon >= 0) + var colonPos = host.LastIndexOf(':'); + var closedSquareBracketPos = host.LastIndexOf(']'); + if (colonPos >= 0 && closedSquareBracketPos < colonPos) { - host = host.Substring(0, colon); + host = host.Substring(0, colonPos); } // var baseUri = $"{(IsSecureConnection ? "https" : "http")}://{host}:{LocalEndPoint.Port}"; diff --git a/src/EmbedIO/Net/Internal/HttpListenerResponse.cs b/src/EmbedIO/Net/Internal/HttpListenerResponse.cs index ff029103..be8703a9 100644 --- a/src/EmbedIO/Net/Internal/HttpListenerResponse.cs +++ b/src/EmbedIO/Net/Internal/HttpListenerResponse.cs @@ -177,8 +177,8 @@ internal MemoryStream SendHeaders(bool closing) { if (_contentType != null) { - var contentTypeValue = _contentType.IndexOf("charset=", StringComparison.Ordinal) == -1 - ? $"{_contentType}; charset={WebServer.DefaultEncoding.WebName}" + var contentTypeValue = _contentType.IndexOf("charset=", StringComparison.Ordinal) == -1 && ContentEncoding is not null + ? $"{_contentType}; charset={ContentEncoding.WebName}" : _contentType; Headers.Add(HttpHeaderNames.ContentType, contentTypeValue); @@ -194,25 +194,37 @@ internal MemoryStream SendHeaders(bool closing) Headers.Add(HttpHeaderNames.Date, HttpDate.Format(DateTime.UtcNow)); } - if (closing) + // HTTP did not support chunked transfer encoding before version 1.1; + // besides, there's no point in setting transfer encoding at all without a request body. + if (closing || ProtocolVersion < HttpVersion.Version11) { - Headers[HttpHeaderNames.ContentLength] = "0"; _chunked = false; } - else + + // Was content length set to a valid value, AND chunked encoding not set? + // Note that this does not mean that a response body _will_ be sent + // as this could be the response to a HEAD request. + var haveContentLength = !_chunked + && Headers.ContainsKey(HttpHeaderNames.ContentLength) + && long.TryParse(Headers[HttpHeaderNames.ContentLength], NumberStyles.None, CultureInfo.InvariantCulture, out var contentLength) + && contentLength >= 0L; + + if (!haveContentLength) { - if (ProtocolVersion < HttpVersion.Version11) + // Content length could have been set to an invalid value (e.g. "-1") + // so we must either force it to 0, or remove the header completely. + if (closing) { - _chunked = false; + // Content length was not explicitly set to a valid value, + // and there is no request body. + Headers[HttpHeaderNames.ContentLength] = "0"; } - - var haveContentLength = !_chunked - && Headers.ContainsKey(HttpHeaderNames.ContentLength) - && long.TryParse(Headers[HttpHeaderNames.ContentLength], out var contentLength) - && contentLength >= 0L; - - if (!haveContentLength) + else { + // Content length was not explicitly set to a valid value, + // and we're going to send a request body. + // - Remove possibly invalid Content-Length header + // - Enable chunked transfer encoding for HTTP 1.1 Headers.Remove(HttpHeaderNames.ContentLength); if (ProtocolVersion >= HttpVersion.Version11) { diff --git a/src/EmbedIO/Net/Internal/ListenerPrefix.cs b/src/EmbedIO/Net/Internal/ListenerPrefix.cs index 11bfc47e..7bd2f1e4 100644 --- a/src/EmbedIO/Net/Internal/ListenerPrefix.cs +++ b/src/EmbedIO/Net/Internal/ListenerPrefix.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; namespace EmbedIO.Net.Internal { @@ -7,44 +6,11 @@ internal sealed class ListenerPrefix { public ListenerPrefix(string uri) { - var defaultPort = 80; - - if (uri.StartsWith("https://", StringComparison.Ordinal)) - { - defaultPort = 443; - Secure = true; - } - - var length = uri.Length; - var startHost = uri.IndexOf(':') + 3; - - if (startHost >= length) - { - throw new ArgumentException("No host specified."); - } - - var colon = uri.LastIndexOf(':'); - int root; - - if (colon > 0) - { - Host = uri.Substring(startHost, colon - startHost); - root = uri.IndexOf('/', colon, length - colon); - Port = int.Parse(uri.Substring(colon + 1, root - colon - 1), CultureInfo.InvariantCulture); - } - else - { - root = uri.IndexOf('/', startHost, length - startHost); - Host = uri.Substring(startHost, root - startHost); - Port = defaultPort; - } - - Path = uri.Substring(root); - - if (Path.Length != 1) - { - Path = Path.Substring(0, Path.Length - 1); - } + var parsedUri = ListenerUri.Parse(uri); + Secure = parsedUri.Secure; + Host = parsedUri.Host; + Port = parsedUri.Port; + Path = parsedUri.Path; } public HttpListener? Listener { get; set; } @@ -59,58 +25,7 @@ public ListenerPrefix(string uri) public static void CheckUri(string uri) { - if (uri == null) - { - throw new ArgumentNullException(nameof(uri)); - } - - if (!uri.StartsWith("http://", StringComparison.Ordinal) && !uri.StartsWith("https://", StringComparison.Ordinal)) - { - throw new ArgumentException("Only 'http' and 'https' schemes are supported."); - } - - var length = uri.Length; - var startHost = uri.IndexOf(':') + 3; - - if (startHost >= length) - { - throw new ArgumentException("No host specified."); - } - - var colon = uri.Substring(startHost).IndexOf(':') > 0 ? uri.LastIndexOf(':') : -1; - - if (startHost == colon) - { - throw new ArgumentException("No host specified."); - } - - int root; - if (colon > 0) - { - root = uri.IndexOf('/', colon, length - colon); - if (root == -1) - { - throw new ArgumentException("No path specified."); - } - - if (!int.TryParse(uri.Substring(colon + 1, root - colon - 1), out var p) || p <= 0 || p >= 65536) - { - throw new ArgumentException("Invalid port."); - } - } - else - { - root = uri.IndexOf('/', startHost, length - startHost); - if (root == -1) - { - throw new ArgumentException("No path specified."); - } - } - - if (uri[uri.Length - 1] != '/') - { - throw new ArgumentException("The prefix must end with '/'"); - } + _ = ListenerUri.Parse(uri); } public bool IsValid() => Path.IndexOf('%') == -1 && Path.IndexOf("//", StringComparison.Ordinal) == -1; diff --git a/src/EmbedIO/Net/Internal/ListenerUri.cs b/src/EmbedIO/Net/Internal/ListenerUri.cs new file mode 100644 index 00000000..e89080d2 --- /dev/null +++ b/src/EmbedIO/Net/Internal/ListenerUri.cs @@ -0,0 +1,91 @@ +using System; + +namespace EmbedIO.Net.Internal +{ + internal class ListenerUri + { + private ListenerUri(bool secure, + string host, + int port, + string path) + { + Secure = secure; + Host = host; + Port = port; + Path = path; + } + + public bool Secure { get; private set; } + + public string Host { get; private set; } + + public int Port { get; private set; } + + public string Path { get; private set; } + + public static ListenerUri Parse(string uri) + { + bool secure; + int port; + int parsingPosition; + if (uri.StartsWith("http://")) + { + secure = false; + port = 80; + parsingPosition = "http://".Length; + } + else if (uri.StartsWith("https://")) + { + secure = true; + port = 443; + parsingPosition = "https://".Length; + } + else + { + throw new Exception("Only 'http' and 'https' schemes are supported."); + } + + var startOfPath = uri.IndexOf('/', parsingPosition); + if (startOfPath == -1) + { + throw new ArgumentException("Path should end in '/'."); + } + + var hostWithPort = uri.Substring(parsingPosition, startOfPath - parsingPosition); + + var startOfPortWithColon = hostWithPort.LastIndexOf(':'); + if (startOfPortWithColon > -1) + { + startOfPortWithColon += parsingPosition; + } + + var endOfIpV6 = hostWithPort.LastIndexOf(']'); + if (endOfIpV6 > -1) + { + endOfIpV6 += parsingPosition; + } + + if (endOfIpV6 > startOfPortWithColon) + { + startOfPortWithColon = -1; + } + + if (startOfPortWithColon != -1 && startOfPortWithColon < startOfPath) + { + if (!int.TryParse(uri.Substring(startOfPortWithColon + 1, startOfPath - startOfPortWithColon - 1), out port) || port <= 0 || port >= 65535) + { + throw new ArgumentException("Invalid port."); + } + } + + var host = uri.Substring(parsingPosition, (startOfPortWithColon == -1 ? startOfPath : startOfPortWithColon) - parsingPosition); + var path = uri.Substring(startOfPath); + if (!path.EndsWith("/")) + { + throw new ArgumentException("Path should end in '/'."); + } + + return new ListenerUri(secure, host, port, path); + } + } +} \ No newline at end of file diff --git a/src/EmbedIO/WebSockets/WebSocketModule.cs b/src/EmbedIO/WebSockets/WebSocketModule.cs index b1d5e522..a4cef0ac 100644 --- a/src/EmbedIO/WebSockets/WebSocketModule.cs +++ b/src/EmbedIO/WebSockets/WebSocketModule.cs @@ -512,7 +512,11 @@ protected virtual void Dispose(bool disposing) private void RemoveWebSocket(IWebSocketContext context) { - _ = _contexts.TryRemove(context.Id, out _); + if (!_contexts.TryRemove(context.Id, out _)) + { + return; + } + context.WebSocket?.Dispose(); // OnClientDisconnectedAsync is better called in its own task, diff --git a/test/EmbedIO.Tests/EmbedIO.Tests.csproj b/test/EmbedIO.Tests/EmbedIO.Tests.csproj index 4dbd71e7..1ad58b4a 100644 --- a/test/EmbedIO.Tests/EmbedIO.Tests.csproj +++ b/test/EmbedIO.Tests/EmbedIO.Tests.csproj @@ -1,7 +1,7 @@  - net5.0 + net6.0 UnitTest diff --git a/test/EmbedIO.Tests/EndToEndFixtureBase.cs b/test/EmbedIO.Tests/EndToEndFixtureBase.cs index 8f17e4e6..b63a78bd 100644 --- a/test/EmbedIO.Tests/EndToEndFixtureBase.cs +++ b/test/EmbedIO.Tests/EndToEndFixtureBase.cs @@ -11,10 +11,12 @@ namespace EmbedIO.Tests public abstract class EndToEndFixtureBase : IDisposable { private readonly bool _useTestWebServer; + private readonly bool _useIPv6; - protected EndToEndFixtureBase(bool useTestWebServer = true) + protected EndToEndFixtureBase(bool useTestWebServer = true, bool useIPv6 = false) { _useTestWebServer = useTestWebServer; + _useIPv6 = useIPv6; } ~EndToEndFixtureBase() @@ -37,7 +39,7 @@ public void Dispose() [SetUp] public void SetUp() { - WebServerUrl = Resources.GetServerAddress(); + WebServerUrl = Resources.GetServerAddress(_useIPv6); if (_useTestWebServer) { diff --git a/test/EmbedIO.Tests/FileModuleTest.cs b/test/EmbedIO.Tests/FileModuleTest.cs index 3b60d8d1..0f3fc54a 100644 --- a/test/EmbedIO.Tests/FileModuleTest.cs +++ b/test/EmbedIO.Tests/FileModuleTest.cs @@ -1,16 +1,15 @@ -using EmbedIO.Tests.TestObjects; -using NUnit.Framework; -using System; +using System; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; -using System.Runtime.InteropServices; using System.Threading.Tasks; using EmbedIO.Testing; +using EmbedIO.Tests.TestObjects; using EmbedIO.Utilities; +using NUnit.Framework; namespace EmbedIO.Tests { @@ -78,11 +77,9 @@ public async Task TestHeadIndex() } [Test] + [Platform(Exclude = "MacOsX", Reason = "OSX doesn't support FileSystemWatcher.")] public async Task FileWritable() { - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - Assert.Ignore("OSX doesn't support FileSystemWatcher"); - var root = Path.GetTempPath(); var file = Path.Combine(root, "index.html"); File.WriteAllText(file, Resources.Index); diff --git a/test/EmbedIO.Tests/HttpsTest.cs b/test/EmbedIO.Tests/HttpsTest.cs index 9a0b23c5..04b738d2 100644 --- a/test/EmbedIO.Tests/HttpsTest.cs +++ b/test/EmbedIO.Tests/HttpsTest.cs @@ -3,11 +3,9 @@ using System.Net.Http; using System.Net.Security; using System.Security.Cryptography.X509Certificates; -using System.Text; using System.Threading.Tasks; using EmbedIO.Tests.TestObjects; using NUnit.Framework; -using Swan; namespace EmbedIO.Tests { @@ -18,11 +16,9 @@ public class HttpsTest private const string HttpsUrl = "https://localhost:5555"; [Test] + [Platform("Win")] public async Task OpenWebServerHttps_RetrievesIndex() { - if (SwanRuntime.OS != Swan.OperatingSystem.Windows) - Assert.Ignore("Only Windows"); - ServicePointManager.ServerCertificateValidationCallback = ValidateCertificate; var options = new WebServerOptions() @@ -41,11 +37,9 @@ public async Task OpenWebServerHttps_RetrievesIndex() } [Test] + [Platform(Exclude = "Win")] public void OpenWebServerHttpsWithLinuxOrMac_ThrowsInvalidOperation() { - if (SwanRuntime.OS == Swan.OperatingSystem.Windows) - Assert.Ignore("Ignore Windows"); - Assert.Throws(() => { var options = new WebServerOptions() .WithUrlPrefix(HttpsUrl) @@ -56,11 +50,9 @@ public void OpenWebServerHttpsWithLinuxOrMac_ThrowsInvalidOperation() } [Test] + [Platform("Win")] public void OpenWebServerHttpsWithoutCert_ThrowsInvalidOperation() { - if (SwanRuntime.OS != Swan.OperatingSystem.Windows) - Assert.Ignore("Only Windows"); - var options = new WebServerOptions() .WithUrlPrefix(HttpsUrl) .WithAutoRegisterCertificate(); @@ -69,11 +61,9 @@ public void OpenWebServerHttpsWithoutCert_ThrowsInvalidOperation() } [Test] + [Platform("Win")] public void OpenWebServerHttpsWithInvalidStore_ThrowsInvalidOperation() { - if (SwanRuntime.OS != Swan.OperatingSystem.Windows) - Assert.Ignore("Only Windows"); - var options = new WebServerOptions() .WithUrlPrefix(HttpsUrl) .WithCertificate(new X509Certificate2()) diff --git a/test/EmbedIO.Tests/IPv6Test.cs b/test/EmbedIO.Tests/IPv6Test.cs index 5c2db09f..961eee83 100644 --- a/test/EmbedIO.Tests/IPv6Test.cs +++ b/test/EmbedIO.Tests/IPv6Test.cs @@ -1,8 +1,7 @@ -using NUnit.Framework; -using Swan; -using System; -using System.Net.Http; +using System.Net.Http; using System.Threading.Tasks; +using EmbedIO.Tests.TestObjects; +using NUnit.Framework; namespace EmbedIO.Tests { @@ -11,13 +10,11 @@ public class IPv6Test { [TestCase("http://[::1]:8877")] [TestCase("http://127.0.0.1:8877")] - public async Task WithUseIpv6_ReturnsValid(string urlTest) + [Platform("Win")] + public async Task WebServer_WithWildcardAddress_RespondsToClient(string urlTest) { - if (SwanRuntime.OS != Swan.OperatingSystem.Windows) - Assert.Ignore("Only Windows"); - var instance = new WebServer(HttpListenerMode.EmbedIO, "http://*:8877"); - instance.OnAny(ctx => ctx.SendDataAsync(DateTime.Now)); + instance.OnAny(Resources.SendTestStringAsync); _= instance.RunAsync(); @@ -26,13 +23,11 @@ public async Task WithUseIpv6_ReturnsValid(string urlTest) } [Test] + [Platform("Win")] public async Task WithIpv6Loopback_ReturnsValid() { - if (SwanRuntime.OS != Swan.OperatingSystem.Windows) - Assert.Ignore("Only Windows"); - var instance = new WebServer(HttpListenerMode.EmbedIO, "http://[::1]:8877"); - instance.OnAny(ctx => ctx.SendDataAsync(DateTime.Now)); + instance.OnAny(Resources.SendTestStringAsync); _ = instance.RunAsync(); diff --git a/test/EmbedIO.Tests/Issues/Issue531_DefaultPort.cs b/test/EmbedIO.Tests/Issues/Issue531_DefaultPort.cs new file mode 100644 index 00000000..cb049786 --- /dev/null +++ b/test/EmbedIO.Tests/Issues/Issue531_DefaultPort.cs @@ -0,0 +1,30 @@ +using System.Collections; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace EmbedIO.Tests.Issues +{ + [TestFixtureSource(nameof(FixtureArgs))] + public class Issue531_DefaultPort : EndToEndFixtureBase + { + private const string TestString = "This is a test."; + private static readonly IEnumerable FixtureArgs = new[] { false, true }; + + public Issue531_DefaultPort(bool useIPv6) + : base(true, useIPv6) + { + } + + protected override void OnSetUp() + { + Server.WithAction("/", HttpVerbs.Get, context => context.SendStringAsync(TestString, MimeType.PlainText, WebServer.DefaultEncoding)); + } + + [Test] + public async Task DefaultPort_Ok() + { + var responseString = await Client.GetStringAsync(WebServerUrl); + Assert.AreEqual(TestString, responseString); + } + } +} \ No newline at end of file diff --git a/test/EmbedIO.Tests/TestObjects/Resources.cs b/test/EmbedIO.Tests/TestObjects/Resources.cs index 0fd09ac8..0a728a24 100644 --- a/test/EmbedIO.Tests/TestObjects/Resources.cs +++ b/test/EmbedIO.Tests/TestObjects/Resources.cs @@ -1,9 +1,12 @@ using System.Threading; +using System.Threading.Tasks; namespace EmbedIO.Tests.TestObjects { public static class Resources { + public static readonly string TestString = "This is a test."; + public static readonly string SubIndex = @" @@ -30,12 +33,17 @@ This is a placeholder private static int _counter = 9699; - public static string GetServerAddress() + public static string GetServerAddress(bool useIPv6 = false) { - const string serverAddress = "http://localhost:{0}/"; + var serverAddress = useIPv6 + ? "http://[::1]:{0}/" + : "http://localhost:{0}/"; Interlocked.Increment(ref _counter); return string.Format(serverAddress, _counter); } + + public static Task SendTestStringAsync(this IHttpContext ctx) + => ctx.SendStringAsync(Resources.TestString, MimeType.PlainText, WebServer.DefaultEncoding); } }