From d3272814bde97d44572f5b179a2b899b70e940b1 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Fri, 20 Sep 2019 11:45:06 +0200 Subject: [PATCH] SEBWIN-314: Completed implementation of simplified filter rule. --- .../Filters/LegacyFilter.cs | 8 +- .../Filters/Rules/SimplifiedRuleTests.cs | 1003 ++++++++++++++--- .../Filters/Rules/SimplifiedRule.cs | 113 +- 3 files changed, 909 insertions(+), 215 deletions(-) diff --git a/SafeExamBrowser.Browser.UnitTests/Filters/LegacyFilter.cs b/SafeExamBrowser.Browser.UnitTests/Filters/LegacyFilter.cs index 6b834c8e..bc8cc540 100644 --- a/SafeExamBrowser.Browser.UnitTests/Filters/LegacyFilter.cs +++ b/SafeExamBrowser.Browser.UnitTests/Filters/LegacyFilter.cs @@ -89,7 +89,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters filterComponent = path; if (filterComponent != null && - !Regex.IsMatch(URLToFilter.AbsolutePath.Trim(new char[] { '/' }), filterComponent.ToString(), RegexOptions.IgnoreCase)) + !Regex.IsMatch(URLToFilter.AbsolutePath.TrimEnd(new char[] { '/' }), filterComponent.ToString(), RegexOptions.IgnoreCase)) { return false; } @@ -404,8 +404,8 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters this.password = regexMatch.Groups[3].Value; this.host = regexMatch.Groups[4].Value; - // Treat a special case when a query is interpreted as part of the host address - if (this.host.Contains("?")) + // Treat a special case when a query or fragment is interpreted as part of the host address + if (this.host.Contains("?") || this.host.Contains("#")) { string splitURLRegexPattern2 = @"([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?"; Regex splitURLRegex2 = new Regex(splitURLRegexPattern2); @@ -434,7 +434,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters this.port = UInt16.Parse(portNumber); } - this.path = regexMatch.Groups[6].Value.Trim(new char[] { '/' }); + this.path = regexMatch.Groups[6].Value.TrimEnd(new char[] { '/' }); this.query = regexMatch.Groups[7].Value; this.fragment = regexMatch.Groups[8].Value; } diff --git a/SafeExamBrowser.Browser.UnitTests/Filters/Rules/SimplifiedRuleTests.cs b/SafeExamBrowser.Browser.UnitTests/Filters/Rules/SimplifiedRuleTests.cs index d514076b..cce6bd45 100644 --- a/SafeExamBrowser.Browser.UnitTests/Filters/Rules/SimplifiedRuleTests.cs +++ b/SafeExamBrowser.Browser.UnitTests/Filters/Rules/SimplifiedRuleTests.cs @@ -7,6 +7,7 @@ */ using System; +using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using SafeExamBrowser.Browser.Contracts.Filters; using SafeExamBrowser.Browser.Filters.Rules; @@ -25,179 +26,6 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules sut = new SimplifiedRule(); } - [TestMethod] - public void TestAlphanumericExpressionAsHost() - { - var expression = "hostname-123"; - var positive = new[] - { - $"scheme://{expression}.org", - $"scheme://www.{expression}.org", - $"scheme://subdomain.{expression}.com", - $"scheme://subdomain-1.subdomain-2.{expression}.org", - $"scheme://user:password@www.{expression}.org/path/file.txt?param=123#fragment" - }; - var negative = new[] - { - $"scheme://hostname.org", - $"scheme://hostname-12.org", - $"scheme://{expression}4.org", - $"scheme://{expression}.realhost.org", - $"scheme://subdomain.{expression}.realhost.org", - $"scheme://www.realhost.{expression}", - $"{expression}://www.host.org", - $"scheme://www.host.org/{expression}/path", - $"scheme://www.host.org/path?param={expression}", - $"scheme://{expression}:password@www.host.org", - $"scheme://user:{expression}@www.host.org", - $"scheme://user:password@www.host.org/path?param=123#{expression}" - }; - - Execute(expression, positive, negative, false); - } - - [TestMethod] - public void TestHostExpressionWithDomain() - { - var expression = "123-hostname.org"; - var positive = new[] - { - $"scheme://{expression}", - $"scheme://www.{expression}", - $"scheme://subdomain.{expression}", - $"scheme://subdomain-1.subdomain-2.{expression}", - $"scheme://user:password@www.{expression}/path/file.txt?param=123#fragment" - }; - var negative = new[] - { - $"scheme://123.org", - $"scheme://123-host.org", - $"scheme://{expression}.com", - $"scheme://{expression}s.org", - $"scheme://{expression}.realhost.org", - $"scheme://subdomain.{expression}.realhost.org", - $"scheme{expression}://www.host.org", - $"scheme://www.host.org/{expression}/path", - $"scheme://www.host.org/path?param={expression}", - $"scheme://{expression}:password@www.host.org", - $"scheme://user:{expression}@www.host.org", - $"scheme://user:password@www.host.org/path?param=123#{expression}" - }; - - Execute(expression, positive, negative); - } - - [TestMethod] - public void TestHostExpressionWithWildcard() - { - var expression = "test.*.org"; - var positive = new[] - { - "scheme://test.host.org", - "scheme://test.host.domain.org", - "scheme://subdomain.test.host.org", - "scheme://user:password@test.domain.org/path/file.txt?param=123#fragment" - }; - var negative = new[] - { - "scheme://test.org", - "scheme://host.com/test.host.org", - "scheme://www.host.org/test.host.org/path", - "scheme://www.host.org/path?param=test.host.org", - "scheme://test.host.org:password@www.host.org", - "scheme://user:test.host.org@www.host.org", - "scheme://user:password@www.host.org/path?param=123#test.host.org" - }; - - Execute(expression, positive, negative); - } - - [TestMethod] - public void TestHostExpressionWithWildcardAsSuffix() - { - var expression = "test.host.*"; - var positive = new[] - { - "scheme://test.host.org", - "scheme://test.host.domain.org", - "scheme://subdomain.test.host.org", - "scheme://user:password@test.host.org/path/file.txt?param=123#fragment" - }; - var negative = new[] - { - "scheme://host.com", - "scheme://test.host", - "scheme://host.com/test.host.txt" - }; - - Execute(expression, positive, negative); - } - - [TestMethod] - public void TestHostExpressionWithWildcardAsPrefix() - { - var expression = "*.org"; - var positive = new[] - { - "scheme://domain.org", - "scheme://test.host.org", - "scheme://test.host.domain.org", - "scheme://user:password@www.host.org/path/file.txt?param=123#fragment" - }; - var negative = new[] - { - "scheme://org", - "scheme://host.com", - "scheme://test.net", - "scheme://test.ch", - "scheme://host.com/test.org" - }; - - Execute(expression, positive, negative); - } - - [TestMethod] - public void TestHostExpressionWithExactSubdomain() - { - var expression = ".www.host.org"; - var positive = new[] - { - "scheme://www.host.org", - "scheme://user:password@www.host.org/path/file.txt?param=123#fragment" - }; - var negative = new[] - { - "scheme://host.org", - "scheme://test.www.host.org", - "scheme://www.host.org.com" - }; - - Execute(expression, positive, negative); - } - - [TestMethod] - public void TestExpressionWithPortNumber() - { - var expression = "host.org:2020"; - var positive = new[] - { - "scheme://host.org:2020", - "scheme://www.host.org:2020", - "scheme://user:password@www.host.org:2020/path/file.txt?param=123#fragment" - }; - var negative = new[] - { - "scheme://host.org", - "scheme://www.host.org", - "scheme://www.host.org:2", - "scheme://www.host.org:20", - "scheme://www.host.org:202", - "scheme://www.host.org:20202" - }; - - Execute(expression, positive, negative); - } - [TestMethod] public void MustIgnoreCase() { @@ -212,19 +40,6 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" })); } - // TODO - //[TestMethod] - //public void MustIgnoreTrailingSlash() - //{ - // Assert.Fail(); - //} - - //[TestMethod] - //public void MustAllowWildcard() - //{ - // Assert.Fail(); - //} - [TestMethod] public void MustInitializeResult() { @@ -262,7 +77,821 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules } } - private void Execute(string expression, string[] positive, string[] negative, bool testLegacy = true) + [TestMethod] + public void TestAlphanumericExpression() + { + var expression = "hostname-123"; + var positive = new[] + { + $"scheme://{expression}", + $"scheme://{expression}.org", // TODO: Is this correct? + $"scheme://www.{expression}.org", + $"scheme://subdomain.{expression}.com", + $"scheme://www.realhost.{expression}", // TODO: Is this correct? + $"scheme://subdomain-1.subdomain-2.{expression}.org", + $"scheme://user:password@www.{expression}.org/path/file.txt?param=123#fragment" + }; + var negative = new[] + { + $"scheme://hostname", + $"scheme://{expression}4", + $"scheme://hostname.org", + $"scheme://hostname-12.org", + $"scheme://{expression}4.org", + $"scheme://{expression}.realhost.org", + $"scheme://subdomain.{expression}.realhost.org", + $"{expression}://www.host.org", + $"scheme://www.host.org/{expression}/path", + $"scheme://www.host.org/path?param={expression}", + $"scheme://{expression}:password@www.host.org", + $"scheme://user:{expression}@www.host.org", + $"scheme://user:password@www.host.org/path?param=123#{expression}" + }; + + Test(expression, positive, negative, false); + } + + [TestMethod] + public void TestAlphanumericExpressionWithWildcard() + { + var expression = "hostname*"; + var positive = new[] + { + "scheme://hostname.org", + "scheme://hostnameabc.org", + "scheme://hostname-12.org", + "scheme://hostname-abc-def-123-456.org", + "scheme://www.hostname-abc.org", + "scheme://www.realhost.hostname", //TODO: Is this correct? + "scheme://subdomain.hostname-xyz.com", + "scheme://hostname.realhost.org", + "scheme://subdomain.hostname.realhost.org", + "scheme://subdomain-1.subdomain-2.hostname-abc-123.org", + "scheme://user:password@www.hostname-abc.org/path/file.txt?param=123#fragment" + }; + var negative = new[] + { + "scheme://hostnam.org", + "hostname://www.host.org", + "scheme://www.host.org/hostname/path", + "scheme://www.host.org/path?param=hostname", + "scheme://hostname:password@www.host.org", + "scheme://user:hostname@www.host.org", + "scheme://user:password@www.host.org/path?param=123#hostname" + }; + + Test(expression, positive, negative, false); + } + + [TestMethod] + public void TestHostWithDomain() + { + var expression = "123-hostname.org"; + var positive = new[] + { + $"scheme://{expression}", + $"scheme://www.{expression}", + $"scheme://subdomain.{expression}", + $"scheme://subdomain-1.subdomain-2.{expression}", + $"scheme://user:password@www.{expression}/path/file.txt?param=123#fragment" + }; + var negative = new[] + { + $"scheme://123.org", + $"scheme://123-host.org", + $"scheme://{expression}.com", + $"scheme://{expression}s.org", + $"scheme://{expression}.realhost.org", + $"scheme://subdomain.{expression}.realhost.org", + $"scheme{expression}://www.host.org", + $"scheme://www.host.org/{expression}/path", + $"scheme://www.host.org/path?param={expression}", + $"scheme://{expression}:password@www.host.org", + $"scheme://user:{expression}@www.host.org", + $"scheme://user:password@www.host.org/path?param=123#{expression}" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestHostWithWildcard() + { + var expression = "test.*.org"; + var positive = new[] + { + "scheme://test.host.org", + "scheme://test.host.domain.org", + "scheme://subdomain.test.host.org", + "scheme://user:password@test.domain.org/path/file.txt?param=123#fragment" + }; + var negative = new[] + { + "scheme://test.org", + "scheme://host.com/test.host.org", + "scheme://www.host.org/test.host.org/path", + "scheme://www.host.org/path?param=test.host.org", + "scheme://test.host.org:password@www.host.org", + "scheme://user:test.host.org@www.host.org", + "scheme://user:password@www.host.org/path?param=123#test.host.org" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestHostWithWildcardAsSuffix() + { + var expression = "test.host.*"; + var positive = new[] + { + "scheme://test.host.org", + "scheme://test.host.domain.org", + "scheme://subdomain.test.host.org", + "scheme://user:password@test.host.org/path/file.txt?param=123#fragment" + }; + var negative = new[] + { + "scheme://host.com", + "scheme://test.host", + "scheme://host.com/test.host.txt" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestHostWithWildcardAsPrefix() + { + var expression = "*.org"; + var positive = new[] + { + "scheme://domain.org", + "scheme://test.host.org", + "scheme://test.host.domain.org", + "scheme://user:password@www.host.org/path/file.txt?param=123#fragment" + }; + var negative = new[] + { + "scheme://org", + "scheme://host.com", + "scheme://test.net", + "scheme://test.ch", + "scheme://host.com/test.org" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestHostWithExactSubdomain() + { + var expression = ".www.host.org"; + var positive = new[] + { + "scheme://www.host.org", + "scheme://user:password@www.host.org/path/file.txt?param=123#fragment" + }; + var negative = new[] + { + "scheme://host.org", + "scheme://test.www.host.org", + "scheme://www.host.org.com" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestPortNumber() + { + var expression = "host.org:2020"; + var positive = new[] + { + "scheme://host.org:2020", + "scheme://www.host.org:2020", + "scheme://user:password@www.host.org:2020/path/file.txt?param=123#fragment" + }; + var negative = new[] + { + "scheme://host.org", + "scheme://www.host.org", + "scheme://www.host.org:2", + "scheme://www.host.org:20", + "scheme://www.host.org:202", + "scheme://www.host.org:20202" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestPortWildcard() + { + var expression = "host.org:*"; + var positive = new[] + { + "scheme://host.org", + "scheme://host.org:0", + "scheme://host.org:1", + "scheme://host.org:2020", + "scheme://host.org:65535", + "scheme://www.host.org", + "scheme://www.host.org:2", + "scheme://www.host.org:20", + "scheme://www.host.org:202", + "scheme://www.host.org:2020", + "scheme://www.host.org:20202", + "scheme://user:password@www.host.org:2020/path/file.txt?param=123#fragment" + }; + + Test(expression, positive, Array.Empty()); + } + + [TestMethod] + public void TestPortNumberWithHostWildcard() + { + var expression = "*:2020"; + var positive = new[] + { + "scheme://host.org:2020", + "scheme://domain.com:2020", + "scheme://user:password@www.server.net:2020/path/file.txt?param=123#fragment" + }; + var negative = new List + { + "scheme://host.org" + }; + + for (var port = 0; port < 65536; port++) + { + if (port != 2020) + { + negative.Add($"{negative[0]}:{port}"); + } + } + + Test(expression, positive, negative.ToArray()); + } + + [TestMethod] + public void TestPath() + { + var expression = "host.org/url/path"; + var positive = new[] + { + "scheme://host.org/url/path", + "scheme://user:password@www.host.org/url/path?param=123#fragment" + }; + var negative = new[] + { + "scheme://host.org", + "scheme://host.org//", + "scheme://host.org///", + "scheme://host.org/url", + "scheme://host.org/path", + "scheme://host.org/url/path.txt", + "scheme://host.org/another/url/path" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestPathWithFile() + { + var expression = "host.org/url/path/to/file.txt"; + var positive = new[] + { + "scheme://host.org/url/path/to/file.txt", + "scheme://subdomain.host.org/url/path/to/file.txt", + "scheme://user:password@www.host.org/url/path/to/file.txt?param=123#fragment" + }; + var negative = new[] + { + "scheme://host.org", + "scheme://host.org/url", + "scheme://host.org/path", + "scheme://host.org/file.txt", + "scheme://host.org/url/path.txt", + "scheme://host.org/url/path/to.txt", + "scheme://host.org/url/path/to/file", + "scheme://host.org/url/path/to/file.", + "scheme://host.org/url/path/to/file.t", + "scheme://host.org/url/path/to/file.tx", + "scheme://host.org/url/path/to/file.txt/segment", + "scheme://host.org/url/path/to/file.txtt", + "scheme://host.org/another/url/path/to/file.txt" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestPathWithWildcard() + { + var expression = "host.org/*/path"; + var positive = new[] + { + "scheme://host.org//path", + "scheme://host.org/url/path", + "scheme://host.org/another/url/path", + "scheme://user:password@www.host.org/yet/another/url/path?param=123#fragment" + }; + var negative = new[] + { + "scheme://host.org", + "scheme://host.org/url", + "scheme://host.org/path", + "scheme://host.org/url/path.txt", + "scheme://host.org/url/path/2" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestPathWithHostWildcard() + { + var expression = "*/url/path"; + var positive = new[] + { + "scheme://local/url/path", + "scheme://host.org/url/path", + "scheme://www.host.org/url/path", + "scheme://another.server.org/url/path", + "scheme://user:password@www.host.org/url/path?param=123#fragment" + }; + var negative = new[] + { + "scheme://host.org", + "scheme://host.org/url", + "scheme://host.org/path", + "scheme://host.org/url/path.txt", + "scheme://host.org/url/path/2" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestPathWithTrailingSlash() + { + var expression = "host.org/url/path/"; + var positive = new[] + { + "scheme://host.org/url/path", + "scheme://host.org/url/path/", + "scheme://user:password@www.host.org/url/path?param=123#fragment", + "scheme://user:password@www.host.org/url/path/?param=123#fragment" + }; + + Test(expression, positive, Array.Empty()); + } + + [TestMethod] + public void TestPathWithoutTrailingSlash() + { + var expression = "host.org/url/path"; + var positive = new[] + { + "scheme://host.org/url/path", + "scheme://host.org/url/path/", + "scheme://user:password@www.host.org/url/path?param=123#fragment", + "scheme://user:password@www.host.org/url/path/?param=123#fragment" + }; + + Test(expression, positive, Array.Empty()); + } + + [TestMethod] + public void TestScheme() + { + var expression = "scheme://host.org"; + var positive = new[] + { + "scheme://host.org", + "scheme://www.host.org", + "scheme://user:password@www.host.org/url/path?param=123#fragment" + }; + var negative = new[] + { + "//host.org", + "https://host.org", + "ftp://host.org", + "ftps://host.org", + "schemes://host.org" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestSchemeWithWildcard() + { + var expression = "*tp://host.org"; + var positive = new[] + { + "tp://host.org", + "ftp://www.host.org", + "http://user:password@www.host.org/url/path?param=123#fragment" + }; + var negative = new[] + { + "//host.org", + "p://host.org", + "https://host.org", + "ftps://host.org" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestSchemeWithHostWildcard() + { + var expression = "scheme://*"; + var positive = new[] + { + "scheme://host", + "scheme://www.server.org", + "scheme://subdomain.domain.org", + "scheme://user:password@www.host.org/url/path?param=123#fragment" + }; + var negative = new[] + { + "//host.org", + "http://host.org", + "https://host.org", + "ftp://host.org", + "ftps://host.org", + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestUserInfoWithName() + { + var expression = "user@host.org"; + var positive = new[] + { + "scheme://user@host.org", + "scheme://user@www.host.org", + "scheme://user:password@host.org", + "scheme://user:password-123@host.org", + "scheme://user:password@www.host.org/url/path?param=123#fragment" + }; + var negative = new[] + { + "scheme://u@host.org", + "scheme://us@host.org", + "scheme://use@host.org", + "scheme://usera@host.org", + "scheme://user@server.net", + "scheme://usertwo@www.host.org" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestUserInfoWithNameWildcard() + { + var expression = "user*@host.org"; + var positive = new[] + { + "scheme://user@host.org", + "scheme://userabc@host.org", + "scheme://user:abc@host.org", + "scheme://user-123@www.host.org", + "scheme://user-123:password@host.org", + "scheme://user-123:password-123@host.org", + "scheme://user-abc-123:password@www.host.org/url/path?param=123#fragment" + }; + var negative = new[] + { + "scheme://u@host.org", + "scheme://us@host.org", + "scheme://use@host.org", + "scheme://user@server.net", + "scheme://usertwo@server.org" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestUserInfoWithPassword() + { + var expression = "user:password@host.org"; + var positive = new[] + { + "scheme://user:password@host.org", + "scheme://user:password@www.host.org", + "scheme://user:password@www.host.org/url/path?param=123#fragment" + }; + var negative = new[] + { + "scheme://u@host.org", + "scheme://us@host.org", + "scheme://use@host.org", + "scheme://user@server.net", + "scheme://usertwo@server.org", + "scheme://user@host.org", + "scheme://userabc@host.org", + "scheme://user:abc@host.org", + "scheme://user-123@www.host.org", + "scheme://user-123:password@host.org", + "scheme://user-123:password@www.host.org/url/path?param=123#fragment", + "scheme://user:password-123@host.org", + "scheme://user:password-123@www.host.org/url/path?param=123#fragment" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestUserInfoWithWildcard() + { + var expression = "*@host.org"; + var positive = new[] + { + "scheme://host.org", + "scheme://user@host.org", + "scheme://user:password@host.org", + "scheme://www.host.org/url/path?param=123#fragment", + "scheme://user@www.host.org/url/path?param=123#fragment", + "scheme://user:password@www.host.org/url/path?param=123#fragment" + }; + var negative = new[] + { + "scheme://server.org", + "scheme://user@server.org", + "scheme://www.server.org/url/path?param=123#fragment", + "scheme://user:password@www.server.org/url/path?param=123#fragment" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestUserInfoWithHostWildcard() + { + var expression = "user:password@*"; + var positive = new[] + { + "scheme://user:password@host.org", + "scheme://user:password@server.net", + "scheme://user:password@www.host.org/url/path?param=123#fragment" + }; + var negative = new[] + { + "scheme://host.org", + "scheme://server.org", + "scheme://user@host.org", + "scheme://user@server.org", + "scheme://password@host.org", + "scheme://www.host.org/url/path?param=123#fragment", + "scheme://www.server.org/url/path?param=123#fragment", + "scheme://user@www.host.org/url/path?param=123#fragment", + "scheme://user@www.server.org/url/path?param=123#fragment", + "scheme://password@www.server.org/url/path?param=123#fragment" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestQuery() + { + var expression = "host.org?param=123"; + var positive = new[] + { + "scheme://host.org?param=123", + "scheme://www.host.org/?param=123", + "scheme://www.host.org/path/?param=123", + "scheme://www.host.org/some/other/random/path?param=123", + "scheme://user:password@www.host.org/url/path?param=123#fragment" + }; + var negative = new[] + { + "scheme://host.org?", + "scheme://host.org?=", + "scheme://host.org?=123", + "scheme://host.org?param=", + "scheme://host.org?param=1", + "scheme://host.org?param=12", + "scheme://host.org?param=1234" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestQueryWithWildcardAsPrefix() + { + var expression = "host.org?*param=123"; + var positive = new[] + { + "scheme://host.org?param=123", + "scheme://www.host.org?param=123", + "scheme://www.host.org/path/?param=123", + "scheme://www.host.org/some/other/random/path?param=123", + "scheme://user:password@www.host.org/url/path?param=123#fragment", + "scheme://host.org?other_param=456¶m=123", + "scheme://host.org?param=123&another_param=123", + "scheme://www.host.org?other_param=456¶m=123", + "scheme://www.host.org/path/?other_param=456¶m=123", + "scheme://www.host.org/some/other/random/path?other_param=456¶m=123", + "scheme://user:password@www.host.org/url/path?other_param=456¶m=123#fragment", + "scheme://host.org?some_param=123469yvuiopwo&another_param=some%20whitespaces%26special%20characters%2B%22%2A%25%C3%A7%2F%28¶m=123" + }; + var negative = new[] + { + "scheme://host.org?", + "scheme://host.org?=", + "scheme://host.org?=123", + "scheme://host.org?aram=123", + "scheme://host.org?param=", + "scheme://host.org?param=1", + "scheme://host.org?param=12", + "scheme://host.org?param=1234", + "scheme://host.org?param=123&another_param=456" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestQueryWithWildcardAsSuffix() + { + var expression = "host.org?param=123*"; + var positive = new[] + { + "scheme://host.org?param=123", + "scheme://host.org?param=1234", + "scheme://www.host.org?param=123", + "scheme://www.host.org/path/?param=123", + "scheme://host.org?param=123&another_param=456", + "scheme://www.host.org/some/other/random/path?param=123", + "scheme://user:password@www.host.org/url/path?param=123#fragment", + "scheme://host.org?param=123&other_param=456", + "scheme://www.host.org/path/?param=123&other_param=456", + "scheme://www.host.org/some/other/random/path?param=123&other_param=456", + "scheme://user:password@www.host.org/url/path?param=123&other_param=456#fragment", + "scheme://host.org?param=123&some_param=123469yvuiopwo&another_param=some%20whitespaces%26special%20characters%2B%22%2A%25%C3%A7%2F%28" + }; + var negative = new[] + { + "scheme://host.org?", + "scheme://host.org?=", + "scheme://host.org?=123", + "scheme://host.org?aram=123", + "scheme://host.org?param=", + "scheme://host.org?param=1", + "scheme://host.org?param=12", + "scheme://host.org?aparam=123", + "scheme://www.host.org?param=456¶m=123", + "scheme://host.org?some_param=123469yvuiopwo&another_param=some%20whitespaces%26special%20characters%2B%22%2A%25%C3%A7%2F%28¶m=123" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestQueryNotAllowed() + { + var expression = "host.org?."; + var positive = new[] + { + "scheme://host.org", + "scheme://host.org?", + "scheme://user:password@www.host.org/url/path#fragment", + "scheme://user:password@www.host.org/url/path?#fragment" + }; + var negative = new[] + { + "scheme://host.org?a", + "scheme://host.org?%20", + "scheme://host.org?=", + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestQueryWithHostWildcard() + { + var expression = "*?param=123"; + var positive = new[] + { + "scheme://host.org?param=123", + "scheme://server.net?param=123", + "scheme://user:password@www.host.org/url/path?param=123#fragment", + "scheme://user:password@www.server.net/url/path?param=123#fragment" + }; + var negative = new[] + { + "scheme://host.org?param=1234", + "scheme://host.org?param=12", + "scheme://host.org?", + "scheme://host.org?param", + "scheme://host.org?123", + "scheme://host.org?=" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestFragment() + { + var expression = "host.org#fragment"; + var positive = new[] + { + "scheme://host.org#fragment", + "scheme://www.host.org#fragment", + "scheme://user:password@www.host.org/url/path/file.txt?param=123#fragment" + }; + var negative = new[] + { + "scheme://host.org", + "scheme://host.org#", + "scheme://host.org#fragmen", + "scheme://host.org#fragment123", + "scheme://host.org#otherfragment" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestFragmentWithWildcardAsPrefix() + { + var expression = "host.org#*fragment"; + var positive = new[] + { + "scheme://host.org#fragment", + "scheme://host.org#somefragment", + "scheme://www.host.org#another_fragment", + "scheme://user:password@www.host.org/url/path/file.txt?param=123#fragment" + }; + var negative = new[] + { + "scheme://host.org", + "scheme://host.org#", + "scheme://host.org#fragmen", + "scheme://host.org#fragment123" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestFragmentWithWildcardAsSuffix() + { + var expression = "host.org#fragment*"; + var positive = new[] + { + "scheme://host.org#fragment", + "scheme://host.org#fragment-123", + "scheme://user:password@www.host.org/url/path/file.txt?param=123#fragment_abc" + }; + var negative = new[] + { + "scheme://host.org", + "scheme://host.org#", + "scheme://host.org#fragmen", + "scheme://www.host.org#another_fragment" + }; + + Test(expression, positive, negative); + } + + [TestMethod] + public void TestFragmentWithHostWildcard() + { + var expression = "*#fragment"; + var positive = new[] + { + "scheme://host.org#fragment", + "scheme://server.net#fragment", + "scheme://user:password@www.host.org/url/path?param=123#fragment", + "scheme://user:password@www.server.net/url/path?param=123#fragment" + }; + var negative = new[] + { + "scheme://host.org", + "scheme://host.org#", + "scheme://host.org#fragmen", + "scheme://host.org#fragment123" + }; + + Test(expression, positive, negative); + } + + private void Test(string expression, string[] positive, string[] negative, bool testLegacy = true) // TODO: Remove flag once issues cleared! { var legacy = new LegacyFilter(expression); diff --git a/SafeExamBrowser.Browser/Filters/Rules/SimplifiedRule.cs b/SafeExamBrowser.Browser/Filters/Rules/SimplifiedRule.cs index 82c64ad3..a2b886aa 100644 --- a/SafeExamBrowser.Browser/Filters/Rules/SimplifiedRule.cs +++ b/SafeExamBrowser.Browser/Filters/Rules/SimplifiedRule.cs @@ -15,16 +15,15 @@ namespace SafeExamBrowser.Browser.Filters.Rules { internal class SimplifiedRule : IRule { - private const string URL_DELIMITER_PATTERN = @"(?:([^\:]*)\:\/\/)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^\/‌​\:]*))?(?:\:([0-9\*]*))?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?"; + private const string URL_DELIMITER_PATTERN = @"(?:([^\:]*)\://)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^/\:\?#]*))?(?:\:([0-9\*]*))?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?"; private Regex fragment; private Regex host; - private Regex password; private Regex path; private int? port; private Regex query; private Regex scheme; - private Regex user; + private Regex userInfo; public FilterResult Result { get; private set; } @@ -41,14 +40,13 @@ namespace SafeExamBrowser.Browser.Filters.Rules var url = new Uri(request.Url, UriKind.Absolute); var isMatch = true; - //isMatch &= scheme == default(Regex) || ...; - //isMatch &= user == default(Regex) || ...; - //isMatch &= password == default(Regex) || ...; + isMatch &= scheme == default(Regex) || scheme.IsMatch(url.Scheme); + isMatch &= userInfo == default(Regex) || userInfo.IsMatch(url.UserInfo); isMatch &= host.IsMatch(url.Host); isMatch &= !port.HasValue || port == url.Port; - //isMatch &= path == default(Regex) || ...; - //isMatch &= query == default(Regex) || ...; - //isMatch &= fragment == default(Regex) || ...; + isMatch &= path == default(Regex) || path.IsMatch(url.AbsolutePath); + isMatch &= query == default(Regex) || query.IsMatch(url.Query); + isMatch &= fragment == default(Regex) || fragment.IsMatch(url.Fragment); return isMatch; } @@ -57,36 +55,58 @@ namespace SafeExamBrowser.Browser.Filters.Rules { var match = Regex.Match(expression, URL_DELIMITER_PATTERN); - //ParseScheme(match.Groups[1].Value); - //ParseUser(match.Groups[2].Value); - //ParsePassword(match.Groups[3].Value); + ParseScheme(match.Groups[1].Value); + ParseUserInfo(match.Groups[2].Value, match.Groups[3].Value); ParseHost(match.Groups[4].Value); ParsePort(match.Groups[5].Value); - //ParsePath(match.Groups[6].Value); - //ParseQuery(match.Groups[7].Value); - //ParseFragment(match.Groups[8].Value); + ParsePath(match.Groups[6].Value); + ParseQuery(match.Groups[7].Value); + ParseFragment(match.Groups[8].Value); + } + + private void ParseScheme(string expression) + { + if (!string.IsNullOrEmpty(expression)) + { + expression = Regex.Escape(expression); + expression = ReplaceWildcard(expression); + + scheme = Build(expression); + } + } + + private void ParseUserInfo(string username, string password) + { + if (!string.IsNullOrEmpty(username)) + { + var expression = default(string); + + username = Regex.Escape(username); + password = Regex.Escape(password); + + expression = string.IsNullOrEmpty(password) ? $@"{username}(:.*)?" : $@"{username}:{password}"; + expression = ReplaceWildcard(expression); + + userInfo = Build(expression); + } } private void ParseHost(string expression) { var hasToplevelDomain = Regex.IsMatch(expression, @"\.+"); var hasSubdomain = Regex.IsMatch(expression, @"\.{2,}"); - var allowOnlyExactSubdomain = expression.StartsWith("."); - - if (allowOnlyExactSubdomain) - { - expression = expression.Substring(1); - } + var matchExactSubdomain = expression.StartsWith("."); + expression = matchExactSubdomain ? expression.Substring(1) : expression; expression = Regex.Escape(expression); expression = ReplaceWildcard(expression); if (!hasToplevelDomain) { - expression = $@"{expression}(\.[a-z]+)"; + expression = $@"{expression}(\.[a-z]+)?"; } - if (!hasSubdomain && !allowOnlyExactSubdomain) + if (!hasSubdomain && !matchExactSubdomain) { expression = $@"(.+?\.)*{expression}"; } @@ -102,6 +122,51 @@ namespace SafeExamBrowser.Browser.Filters.Rules } } + private void ParsePath(string expression) + { + if (!string.IsNullOrWhiteSpace(expression)) + { + expression = Regex.Escape(expression); + expression = ReplaceWildcard(expression); + expression = expression.EndsWith("/") ? $@"{expression}?" : $@"{expression}/?"; + + path = Build(expression); + } + } + + private void ParseQuery(string expression) + { + if (!string.IsNullOrWhiteSpace(expression)) + { + var noQueryAllowed = expression == "."; + + if (noQueryAllowed) + { + expression = @"\??"; + } + else + { + expression = Regex.Escape(expression); + expression = ReplaceWildcard(expression); + expression = $@"\??{expression}"; + } + + query = Build(expression); + } + } + + private void ParseFragment(string expression) + { + if (!string.IsNullOrWhiteSpace(expression)) + { + expression = Regex.Escape(expression); + expression = ReplaceWildcard(expression); + expression = $"#?{expression}"; + + fragment = Build(expression); + } + } + private Regex Build(string expression) { return new Regex($"^{expression}$", RegexOptions.IgnoreCase); @@ -109,7 +174,7 @@ namespace SafeExamBrowser.Browser.Filters.Rules private string ReplaceWildcard(string expression) { - return expression.Replace(@"\*", ".*?"); + return expression.Replace(@"\*", ".*"); } private void ValidateExpression(string expression)