SEBWIN-314: Started implementing filter rules with unit tests.
This commit is contained in:
		
							parent
							
								
									5209103c97
								
							
						
					
					
						commit
						6d1b282b33
					
				
					 7 changed files with 995 additions and 3 deletions
				
			
		
							
								
								
									
										553
									
								
								SafeExamBrowser.Browser.UnitTests/Filters/LegacyFilter.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										553
									
								
								SafeExamBrowser.Browser.UnitTests/Filters/LegacyFilter.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,553 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
 | 
			
		||||
 * 
 | 
			
		||||
 * This Source Code Form is subject to the terms of the Mozilla internal
 | 
			
		||||
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
			
		||||
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace SafeExamBrowser.Browser.UnitTests.Filters
 | 
			
		||||
{
 | 
			
		||||
	internal class LegacyFilter
 | 
			
		||||
	{
 | 
			
		||||
		internal Regex scheme;
 | 
			
		||||
		internal Regex user;
 | 
			
		||||
		internal Regex password;
 | 
			
		||||
		internal Regex host;
 | 
			
		||||
		internal int? port;
 | 
			
		||||
		internal Regex path;
 | 
			
		||||
		internal Regex query;
 | 
			
		||||
		internal Regex fragment;
 | 
			
		||||
 | 
			
		||||
		internal LegacyFilter(string filterExpressionString)
 | 
			
		||||
		{
 | 
			
		||||
			SEBURLFilterExpression URLFromString = new SEBURLFilterExpression(filterExpressionString);
 | 
			
		||||
			try
 | 
			
		||||
			{
 | 
			
		||||
				this.scheme = RegexForFilterString(URLFromString.scheme);
 | 
			
		||||
				this.user = RegexForFilterString(URLFromString.user);
 | 
			
		||||
				this.password = RegexForFilterString(URLFromString.password);
 | 
			
		||||
				this.host = RegexForHostFilterString(URLFromString.host);
 | 
			
		||||
				this.port = URLFromString.port;
 | 
			
		||||
				this.path = RegexForPathFilterString(URLFromString.path);
 | 
			
		||||
				this.query = RegexForQueryFilterString(URLFromString.query);
 | 
			
		||||
				this.fragment = RegexForFilterString(URLFromString.fragment);
 | 
			
		||||
			}
 | 
			
		||||
			catch (Exception)
 | 
			
		||||
			{
 | 
			
		||||
				throw;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Method comparing all components of a passed URL with the filter expression
 | 
			
		||||
		// and returning YES (= allow or block) if it matches
 | 
			
		||||
		internal bool IsMatch(Uri URLToFilter)
 | 
			
		||||
		{
 | 
			
		||||
			Regex filterComponent;
 | 
			
		||||
 | 
			
		||||
			// If a scheme is indicated in the filter expression, it has to match
 | 
			
		||||
			filterComponent = scheme;
 | 
			
		||||
			UriBuilder urlToFilterParts = new UriBuilder(URLToFilter);
 | 
			
		||||
 | 
			
		||||
			if (filterComponent != null &&
 | 
			
		||||
				!Regex.IsMatch(URLToFilter.Scheme, filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
			
		||||
			{
 | 
			
		||||
				// Scheme of the URL to filter doesn't match the one from the filter expression: Exit with matching = NO
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			string userInfo = URLToFilter.UserInfo;
 | 
			
		||||
			filterComponent = user;
 | 
			
		||||
			if (filterComponent != null &&
 | 
			
		||||
				!Regex.IsMatch(urlToFilterParts.UserName, filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
			
		||||
			{
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			filterComponent = password;
 | 
			
		||||
			if (filterComponent != null &&
 | 
			
		||||
				!Regex.IsMatch(urlToFilterParts.Password, filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
			
		||||
			{
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			filterComponent = host;
 | 
			
		||||
			if (filterComponent != null &&
 | 
			
		||||
				!Regex.IsMatch(URLToFilter.Host, filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
			
		||||
			{
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (port != null && URLToFilter.Port != port)
 | 
			
		||||
			{
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			filterComponent = path;
 | 
			
		||||
			if (filterComponent != null &&
 | 
			
		||||
				!Regex.IsMatch(URLToFilter.AbsolutePath.Trim(new char[] { '/' }), filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
			
		||||
			{
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			string urlQuery = URLToFilter.GetComponents(UriComponents.Query, UriFormat.Unescaped);
 | 
			
		||||
			filterComponent = query;
 | 
			
		||||
			if (filterComponent != null)
 | 
			
		||||
			{
 | 
			
		||||
				// If there's a query filter component, then we need to even filter empty URL query strings
 | 
			
		||||
				// as the filter might either allow some specific queries or no query at all ("?." query filter)
 | 
			
		||||
				if (urlQuery == null)
 | 
			
		||||
				{
 | 
			
		||||
					urlQuery = "";
 | 
			
		||||
				}
 | 
			
		||||
				if (!Regex.IsMatch(urlQuery, filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
			
		||||
				{
 | 
			
		||||
					return false;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			string urlFragment = URLToFilter.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
 | 
			
		||||
			filterComponent = fragment;
 | 
			
		||||
			if (filterComponent != null &&
 | 
			
		||||
				!Regex.IsMatch(urlFragment, filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
			
		||||
			{
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// URL matches the filter expression
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal static Regex RegexForFilterString(string filterString)
 | 
			
		||||
		{
 | 
			
		||||
			if (string.IsNullOrEmpty(filterString))
 | 
			
		||||
			{
 | 
			
		||||
				return null;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				string regexString = Regex.Escape(filterString);
 | 
			
		||||
				regexString = regexString.Replace("\\*", ".*?");
 | 
			
		||||
				// Add regex command characters for matching at start and end of a line (part)
 | 
			
		||||
				regexString = string.Format("^{0}$", regexString);
 | 
			
		||||
 | 
			
		||||
				try
 | 
			
		||||
				{
 | 
			
		||||
					Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
 | 
			
		||||
					return regex;
 | 
			
		||||
				}
 | 
			
		||||
				catch (Exception)
 | 
			
		||||
				{
 | 
			
		||||
					throw;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal static Regex RegexForHostFilterString(string filterString)
 | 
			
		||||
		{
 | 
			
		||||
			if (string.IsNullOrEmpty(filterString))
 | 
			
		||||
			{
 | 
			
		||||
				return null;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				try
 | 
			
		||||
				{
 | 
			
		||||
					// Check if host string has a dot "." prefix to disable subdomain matching
 | 
			
		||||
					if (filterString.Length > 1 && filterString.StartsWith("."))
 | 
			
		||||
					{
 | 
			
		||||
						// Get host string without the "." prefix
 | 
			
		||||
						filterString = filterString.Substring(1);
 | 
			
		||||
						// Get regex for host <*://example.com> (without possible subdomains)
 | 
			
		||||
						return RegexForFilterString(filterString);
 | 
			
		||||
					}
 | 
			
		||||
					// Allow subdomain matching: Create combined regex for <example.com> and <*.example.com>
 | 
			
		||||
					string regexString = Regex.Escape(filterString);
 | 
			
		||||
					regexString = regexString.Replace("\\*", ".*?");
 | 
			
		||||
					// Add regex command characters for matching at start and end of a line (part)
 | 
			
		||||
					regexString = string.Format("^(({0})|(.*?\\.{0}))$", regexString);
 | 
			
		||||
					Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
 | 
			
		||||
					return regex;
 | 
			
		||||
				}
 | 
			
		||||
				catch (Exception)
 | 
			
		||||
				{
 | 
			
		||||
					throw;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal static Regex RegexForPathFilterString(string filterString)
 | 
			
		||||
		{
 | 
			
		||||
			// Trim a possible trailing slash "/", we will instead add a rule to also match paths to directories without trailing slash
 | 
			
		||||
			filterString = filterString.TrimEnd(new char[] { '/' });
 | 
			
		||||
			;
 | 
			
		||||
 | 
			
		||||
			if (string.IsNullOrEmpty(filterString))
 | 
			
		||||
			{
 | 
			
		||||
				return null;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				try
 | 
			
		||||
				{
 | 
			
		||||
					// Check if path string ends with a "/*" for matching contents of a directory
 | 
			
		||||
					if (filterString.EndsWith("/*"))
 | 
			
		||||
					{
 | 
			
		||||
						// As the path filter string matches for a directory, we need to add a string to match directories without trailing slash
 | 
			
		||||
 | 
			
		||||
						// Get path string without the "/*" suffix
 | 
			
		||||
						string filterStringDirectory = filterString.Substring(0, filterString.Length - 2);
 | 
			
		||||
 | 
			
		||||
						string regexString = Regex.Escape(filterString);
 | 
			
		||||
						regexString = regexString.Replace("\\*", ".*?");
 | 
			
		||||
 | 
			
		||||
						string regexStringDir = Regex.Escape(filterString);
 | 
			
		||||
						regexStringDir = regexStringDir.Replace("\\*", ".*?");
 | 
			
		||||
 | 
			
		||||
						// Add regex command characters for matching at start and end of a line (part)
 | 
			
		||||
						regexString = string.Format("^(({0})|({1}))$", regexString, regexStringDir);
 | 
			
		||||
 | 
			
		||||
						Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
 | 
			
		||||
						return regex;
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						return RegexForFilterString(filterString);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				catch (Exception)
 | 
			
		||||
				{
 | 
			
		||||
					throw;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal static Regex RegexForQueryFilterString(string filterString)
 | 
			
		||||
		{
 | 
			
		||||
			if (string.IsNullOrEmpty(filterString))
 | 
			
		||||
			{
 | 
			
		||||
				return null;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				if (filterString.Equals("."))
 | 
			
		||||
				{
 | 
			
		||||
					// Add regex command characters for matching at start and end of a line (part)
 | 
			
		||||
					// and regex for no string allowed
 | 
			
		||||
					string regexString = @"^$";
 | 
			
		||||
					try
 | 
			
		||||
					{
 | 
			
		||||
						Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
 | 
			
		||||
						return regex;
 | 
			
		||||
					}
 | 
			
		||||
					catch (Exception)
 | 
			
		||||
					{
 | 
			
		||||
						throw;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					return RegexForFilterString(filterString);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override string ToString()
 | 
			
		||||
		{
 | 
			
		||||
			StringBuilder expressionString = new StringBuilder();
 | 
			
		||||
			string part;
 | 
			
		||||
			expressionString.Append("^");
 | 
			
		||||
 | 
			
		||||
			/// Scheme
 | 
			
		||||
			if (this.scheme != null)
 | 
			
		||||
			{
 | 
			
		||||
				// If there is a regex filter for scheme
 | 
			
		||||
				// get stripped regex pattern
 | 
			
		||||
				part = StringForRegexFilter(this.scheme);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				// otherwise use the regex wildcard pattern for scheme
 | 
			
		||||
				part = @".*?";
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			expressionString.AppendFormat("{0}:\\/\\/", part);
 | 
			
		||||
 | 
			
		||||
			/// User/Password
 | 
			
		||||
			if (this.user != null)
 | 
			
		||||
			{
 | 
			
		||||
				part = StringForRegexFilter(this.user);
 | 
			
		||||
 | 
			
		||||
				expressionString.Append(part);
 | 
			
		||||
 | 
			
		||||
				if (this.password != null)
 | 
			
		||||
				{
 | 
			
		||||
					expressionString.AppendFormat(":{0}@", StringForRegexFilter(this.password));
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					expressionString.Append("@");
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/// Host
 | 
			
		||||
			string hostPort = "";
 | 
			
		||||
			if (this.host != null)
 | 
			
		||||
			{
 | 
			
		||||
				hostPort = StringForRegexFilter(this.host);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				hostPort = ".*?";
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/// Port
 | 
			
		||||
			if (this.port != null && this.port > 0 && this.port <= 65535)
 | 
			
		||||
			{
 | 
			
		||||
				hostPort = string.Format("{0}:{1}", hostPort, this.port);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// When there is a host, but no path
 | 
			
		||||
			if (this.host != null && this.path == null)
 | 
			
		||||
			{
 | 
			
		||||
				hostPort = string.Format("(({0})|({0}\\/.*?))", hostPort);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			expressionString.Append(hostPort);
 | 
			
		||||
 | 
			
		||||
			/// Path
 | 
			
		||||
			if (this.path != null)
 | 
			
		||||
			{
 | 
			
		||||
				string path = StringForRegexFilter(this.path);
 | 
			
		||||
				if (path.StartsWith("\\/"))
 | 
			
		||||
				{
 | 
			
		||||
					expressionString.Append(path);
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					expressionString.AppendFormat("\\/{0}", path);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/// Query
 | 
			
		||||
			if (this.query != null)
 | 
			
		||||
			{
 | 
			
		||||
				// Check for special case Query = "?." which means no query string is allowed
 | 
			
		||||
				if (StringForRegexFilter(this.query).Equals("."))
 | 
			
		||||
				{
 | 
			
		||||
					expressionString.AppendFormat("[^\\?]");
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					expressionString.AppendFormat("\\?{0}", StringForRegexFilter(this.query));
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				expressionString.AppendFormat("(()|(\\?.*?))");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/// Fragment
 | 
			
		||||
			if (this.fragment != null)
 | 
			
		||||
			{
 | 
			
		||||
				expressionString.AppendFormat("#{0}", StringForRegexFilter(this.fragment));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			expressionString.Append("$");
 | 
			
		||||
 | 
			
		||||
			return expressionString.ToString();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		internal string StringForRegexFilter(Regex regexFilter)
 | 
			
		||||
		{
 | 
			
		||||
			// Get pattern string from regular expression
 | 
			
		||||
			string regexPattern = regexFilter.ToString();
 | 
			
		||||
			if (regexPattern.Length <= 2)
 | 
			
		||||
			{
 | 
			
		||||
				return "";
 | 
			
		||||
			}
 | 
			
		||||
			// Remove the regex command characters for matching at start and end of a line
 | 
			
		||||
			regexPattern = regexPattern.Substring(1, regexPattern.Length - 2);
 | 
			
		||||
			return regexPattern;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private class SEBURLFilterExpression
 | 
			
		||||
		{
 | 
			
		||||
			internal string scheme;
 | 
			
		||||
			internal string user;
 | 
			
		||||
			internal string password;
 | 
			
		||||
			internal string host;
 | 
			
		||||
			internal int? port;
 | 
			
		||||
			internal string path;
 | 
			
		||||
			internal string query;
 | 
			
		||||
			internal string fragment;
 | 
			
		||||
 | 
			
		||||
			internal SEBURLFilterExpression(string filterExpressionString)
 | 
			
		||||
			{
 | 
			
		||||
				if (!string.IsNullOrEmpty(filterExpressionString))
 | 
			
		||||
				{
 | 
			
		||||
					/// Convert Uri to a SEBURLFilterExpression
 | 
			
		||||
					string splitURLRegexPattern = @"(?:([^\:]*)\:\/\/)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^\/\:]*))?(?:\:([0-9\*]*))?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
 | 
			
		||||
					Regex splitURLRegex = new Regex(splitURLRegexPattern);
 | 
			
		||||
					Match regexMatch = splitURLRegex.Match(filterExpressionString);
 | 
			
		||||
					if (regexMatch.Success == false)
 | 
			
		||||
					{
 | 
			
		||||
						return;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					this.scheme = regexMatch.Groups[1].Value;
 | 
			
		||||
					this.user = regexMatch.Groups[2].Value;
 | 
			
		||||
					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("?"))
 | 
			
		||||
					{
 | 
			
		||||
						string splitURLRegexPattern2 = @"([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
 | 
			
		||||
						Regex splitURLRegex2 = new Regex(splitURLRegexPattern2);
 | 
			
		||||
						Match regexMatch2 = splitURLRegex2.Match(this.host);
 | 
			
		||||
						if (regexMatch.Success == false)
 | 
			
		||||
						{
 | 
			
		||||
							return;
 | 
			
		||||
						}
 | 
			
		||||
						this.host = regexMatch2.Groups[1].Value;
 | 
			
		||||
						this.port = null;
 | 
			
		||||
						this.path = "";
 | 
			
		||||
						this.query = regexMatch2.Groups[2].Value;
 | 
			
		||||
						this.fragment = regexMatch2.Groups[3].Value;
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						string portNumber = regexMatch.Groups[5].Value;
 | 
			
		||||
 | 
			
		||||
						// We only want a port if the filter expression string explicitely defines one!
 | 
			
		||||
						if (portNumber.Length == 0 || portNumber == "*")
 | 
			
		||||
						{
 | 
			
		||||
							this.port = null;
 | 
			
		||||
						}
 | 
			
		||||
						else
 | 
			
		||||
						{
 | 
			
		||||
							this.port = UInt16.Parse(portNumber);
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						this.path = regexMatch.Groups[6].Value.Trim(new char[] { '/' });
 | 
			
		||||
						this.query = regexMatch.Groups[7].Value;
 | 
			
		||||
						this.fragment = regexMatch.Groups[8].Value;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			internal static string User(string userInfo)
 | 
			
		||||
			{
 | 
			
		||||
				string user = "";
 | 
			
		||||
				if (!string.IsNullOrEmpty(userInfo))
 | 
			
		||||
				{
 | 
			
		||||
					int userPasswordSeparator = userInfo.IndexOf(":");
 | 
			
		||||
					if (userPasswordSeparator == -1)
 | 
			
		||||
					{
 | 
			
		||||
						user = userInfo;
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						if (userPasswordSeparator != 0)
 | 
			
		||||
						{
 | 
			
		||||
							user = userInfo.Substring(0, userPasswordSeparator);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				return user;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			internal static string Password(string userInfo)
 | 
			
		||||
			{
 | 
			
		||||
				string password = "";
 | 
			
		||||
				if (!string.IsNullOrEmpty(userInfo))
 | 
			
		||||
				{
 | 
			
		||||
					int userPasswordSeparator = userInfo.IndexOf(":");
 | 
			
		||||
					if (userPasswordSeparator != -1)
 | 
			
		||||
					{
 | 
			
		||||
						if (userPasswordSeparator < userInfo.Length - 1)
 | 
			
		||||
						{
 | 
			
		||||
							password = userInfo.Substring(userPasswordSeparator + 1, userInfo.Length - 1 - userPasswordSeparator);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				return password;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			internal SEBURLFilterExpression(string scheme, string user, string password, string host, int port, string path, string query, string fragment)
 | 
			
		||||
			{
 | 
			
		||||
				this.scheme = scheme;
 | 
			
		||||
				this.user = user;
 | 
			
		||||
				this.password = password;
 | 
			
		||||
				this.host = host;
 | 
			
		||||
				this.port = port;
 | 
			
		||||
				this.path = path;
 | 
			
		||||
				this.query = query;
 | 
			
		||||
				this.fragment = fragment;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			public override string ToString()
 | 
			
		||||
			{
 | 
			
		||||
				StringBuilder expressionString = new StringBuilder();
 | 
			
		||||
				if (!string.IsNullOrEmpty(this.scheme))
 | 
			
		||||
				{
 | 
			
		||||
					if (!string.IsNullOrEmpty(this.host))
 | 
			
		||||
					{
 | 
			
		||||
						expressionString.AppendFormat("{0}://", this.scheme);
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						expressionString.AppendFormat("{0}:", this.scheme);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if (!string.IsNullOrEmpty(this.user))
 | 
			
		||||
				{
 | 
			
		||||
					expressionString.Append(this.user);
 | 
			
		||||
 | 
			
		||||
					if (!string.IsNullOrEmpty(this.password))
 | 
			
		||||
					{
 | 
			
		||||
						expressionString.AppendFormat(":{0}@", this.password);
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						expressionString.Append("@");
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if (!string.IsNullOrEmpty(this.host))
 | 
			
		||||
				{
 | 
			
		||||
					expressionString.Append(this.host);
 | 
			
		||||
				}
 | 
			
		||||
				if (this.port != null && this.port > 0 && this.port <= 65535)
 | 
			
		||||
				{
 | 
			
		||||
					expressionString.AppendFormat(":{0}", this.port);
 | 
			
		||||
				}
 | 
			
		||||
				if (!string.IsNullOrEmpty(this.path))
 | 
			
		||||
				{
 | 
			
		||||
					if (this.path.StartsWith("/"))
 | 
			
		||||
					{
 | 
			
		||||
						expressionString.Append(this.path);
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						expressionString.AppendFormat("/{0}", this.path);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if (!string.IsNullOrEmpty(this.query))
 | 
			
		||||
				{
 | 
			
		||||
					expressionString.AppendFormat("?{0}", this.query);
 | 
			
		||||
				}
 | 
			
		||||
				if (!string.IsNullOrEmpty(this.fragment))
 | 
			
		||||
				{
 | 
			
		||||
					expressionString.AppendFormat("#{0}", this.fragment);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return expressionString.ToString();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -6,8 +6,12 @@
 | 
			
		|||
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
 | 
			
		||||
using SafeExamBrowser.Browser.Contracts.Filters;
 | 
			
		||||
using SafeExamBrowser.Browser.Filters.Rules;
 | 
			
		||||
using SafeExamBrowser.Settings.Browser;
 | 
			
		||||
 | 
			
		||||
namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -21,5 +25,43 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
 | 
			
		|||
		{
 | 
			
		||||
			sut = new RegexRule();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[TestMethod]
 | 
			
		||||
		public void MustIgnoreCase()
 | 
			
		||||
		{
 | 
			
		||||
			sut.Initialize(new FilterRuleSettings { Expression = Regex.Escape("http://www.test.org/path/file.txt?param=123") });
 | 
			
		||||
 | 
			
		||||
			Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
 | 
			
		||||
			Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
 | 
			
		||||
 | 
			
		||||
			sut.Initialize(new FilterRuleSettings { Expression = Regex.Escape("HTTP://WWW.TEST.ORG/PATH/FILE.TXT?PARAM=123") });
 | 
			
		||||
 | 
			
		||||
			Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
 | 
			
		||||
			Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[TestMethod]
 | 
			
		||||
		public void MustInitializeResult()
 | 
			
		||||
		{
 | 
			
		||||
			foreach (var result in Enum.GetValues(typeof(FilterResult)))
 | 
			
		||||
			{
 | 
			
		||||
				sut.Initialize(new FilterRuleSettings { Expression = "", Result = (FilterResult) result });
 | 
			
		||||
				Assert.AreEqual(result, sut.Result);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[TestMethod]
 | 
			
		||||
		[ExpectedException(typeof(ArgumentNullException))]
 | 
			
		||||
		public void MustNotAllowUndefinedExpression()
 | 
			
		||||
		{
 | 
			
		||||
			sut.Initialize(new FilterRuleSettings());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[TestMethod]
 | 
			
		||||
		[ExpectedException(typeof(ArgumentException))]
 | 
			
		||||
		public void MustValidateExpression()
 | 
			
		||||
		{
 | 
			
		||||
			sut.Initialize(new FilterRuleSettings { Expression = "ç+\"}%&*/(+)=?{=*+¦]@#°§]`?´^¨'°[¬|¢" });
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,8 +6,11 @@
 | 
			
		|||
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
 | 
			
		||||
using SafeExamBrowser.Browser.Contracts.Filters;
 | 
			
		||||
using SafeExamBrowser.Browser.Filters.Rules;
 | 
			
		||||
using SafeExamBrowser.Settings.Browser;
 | 
			
		||||
 | 
			
		||||
namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -21,5 +24,269 @@ 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()
 | 
			
		||||
		{
 | 
			
		||||
			sut.Initialize(new FilterRuleSettings { Expression = "http://www.test.org/path/file.txt?param=123" });
 | 
			
		||||
 | 
			
		||||
			Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
 | 
			
		||||
			Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
 | 
			
		||||
 | 
			
		||||
			sut.Initialize(new FilterRuleSettings { Expression = "HTTP://WWW.TEST.ORG/PATH/FILE.TXT?PARAM=123" });
 | 
			
		||||
 | 
			
		||||
			Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
 | 
			
		||||
			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()
 | 
			
		||||
		{
 | 
			
		||||
			foreach (var result in Enum.GetValues(typeof(FilterResult)))
 | 
			
		||||
			{
 | 
			
		||||
				sut.Initialize(new FilterRuleSettings { Expression = "*", Result = (FilterResult) result });
 | 
			
		||||
				Assert.AreEqual(result, sut.Result);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[TestMethod]
 | 
			
		||||
		[ExpectedException(typeof(ArgumentNullException))]
 | 
			
		||||
		public void MustNotAllowUndefinedExpression()
 | 
			
		||||
		{
 | 
			
		||||
			sut.Initialize(new FilterRuleSettings());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[TestMethod]
 | 
			
		||||
		public void MustValidateExpression()
 | 
			
		||||
		{
 | 
			
		||||
			var invalid = new[]
 | 
			
		||||
			{
 | 
			
		||||
				".", "+", "\"", "ç", "%", "&", "/", "(", ")", "=", "?", "^", "!", "[", "]", "{", "}", "¦", "@", "#", "°", "§", "¬", "|", "¢", "´", "'", "`", "~", "<", ">", "\\"
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			sut.Initialize(new FilterRuleSettings { Expression = "*" });
 | 
			
		||||
			sut.Initialize(new FilterRuleSettings { Expression = "a" });
 | 
			
		||||
			sut.Initialize(new FilterRuleSettings { Expression = "A" });
 | 
			
		||||
			sut.Initialize(new FilterRuleSettings { Expression = "0" });
 | 
			
		||||
			sut.Initialize(new FilterRuleSettings { Expression = "abcdeFGHIJK-12345" });
 | 
			
		||||
 | 
			
		||||
			foreach (var expression in invalid)
 | 
			
		||||
			{
 | 
			
		||||
				Assert.ThrowsException<ArgumentException>(() => sut.Initialize(new FilterRuleSettings { Expression = expression }));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void Execute(string expression, string[] positive, string[] negative, bool testLegacy = true)
 | 
			
		||||
		{
 | 
			
		||||
			var legacy = new LegacyFilter(expression);
 | 
			
		||||
 | 
			
		||||
			sut.Initialize(new FilterRuleSettings { Expression = expression });
 | 
			
		||||
 | 
			
		||||
			foreach (var url in positive)
 | 
			
		||||
			{
 | 
			
		||||
				Assert.IsTrue(sut.IsMatch(new Request { Url = url }), url);
 | 
			
		||||
 | 
			
		||||
				if (testLegacy)
 | 
			
		||||
				{
 | 
			
		||||
					Assert.IsTrue(legacy.IsMatch(new Uri(url)), url);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			foreach (var url in negative)
 | 
			
		||||
			{
 | 
			
		||||
				Assert.IsFalse(sut.IsMatch(new Request { Url = url }), url);
 | 
			
		||||
 | 
			
		||||
				if (testLegacy)
 | 
			
		||||
				{
 | 
			
		||||
					Assert.IsFalse(legacy.IsMatch(new Uri(url)), url);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,6 +80,7 @@
 | 
			
		|||
    </Reference>
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <Compile Include="Filters\LegacyFilter.cs" />
 | 
			
		||||
    <Compile Include="Filters\RequestFilterTests.cs" />
 | 
			
		||||
    <Compile Include="Filters\RuleFactoryTests.cs" />
 | 
			
		||||
    <Compile Include="Filters\Rules\RegexRuleTests.cs" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@
 | 
			
		|||
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using SafeExamBrowser.Browser.Contracts.Filters;
 | 
			
		||||
using SafeExamBrowser.Settings.Browser;
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +21,8 @@ namespace SafeExamBrowser.Browser.Filters.Rules
 | 
			
		|||
 | 
			
		||||
		public void Initialize(FilterRuleSettings settings)
 | 
			
		||||
		{
 | 
			
		||||
			ValidateExpression(settings.Expression);
 | 
			
		||||
 | 
			
		||||
			expression = settings.Expression;
 | 
			
		||||
			Result = settings.Result;
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -28,5 +31,22 @@ namespace SafeExamBrowser.Browser.Filters.Rules
 | 
			
		|||
		{
 | 
			
		||||
			return Regex.IsMatch(request.Url, expression, RegexOptions.IgnoreCase);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void ValidateExpression(string expression)
 | 
			
		||||
		{
 | 
			
		||||
			if (expression == default(string))
 | 
			
		||||
			{
 | 
			
		||||
				throw new ArgumentNullException(nameof(expression));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			try
 | 
			
		||||
			{
 | 
			
		||||
				Regex.Match("", expression);
 | 
			
		||||
			}
 | 
			
		||||
			catch (Exception e)
 | 
			
		||||
			{
 | 
			
		||||
				throw new ArgumentException($"Invalid regular expression!", nameof(expression), e);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@
 | 
			
		|||
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using SafeExamBrowser.Browser.Contracts.Filters;
 | 
			
		||||
using SafeExamBrowser.Settings.Browser;
 | 
			
		||||
| 
						 | 
				
			
			@ -14,19 +15,123 @@ namespace SafeExamBrowser.Browser.Filters.Rules
 | 
			
		|||
{
 | 
			
		||||
	internal class SimplifiedRule : IRule
 | 
			
		||||
	{
 | 
			
		||||
		private string expression;
 | 
			
		||||
		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;
 | 
			
		||||
 | 
			
		||||
		public FilterResult Result { get; private set; }
 | 
			
		||||
 | 
			
		||||
		public void Initialize(FilterRuleSettings settings)
 | 
			
		||||
		{
 | 
			
		||||
			expression = settings.Expression.Replace("*", @".*");
 | 
			
		||||
			ValidateExpression(settings.Expression);
 | 
			
		||||
			ParseExpression(settings.Expression);
 | 
			
		||||
 | 
			
		||||
			Result = settings.Result;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool IsMatch(Request request)
 | 
			
		||||
		{
 | 
			
		||||
			return Regex.IsMatch(request.Url, expression, RegexOptions.IgnoreCase);
 | 
			
		||||
			var url = new Uri(request.Url, UriKind.Absolute);
 | 
			
		||||
			var isMatch = true;
 | 
			
		||||
 | 
			
		||||
			//isMatch &= scheme == default(Regex) || ...;
 | 
			
		||||
			//isMatch &= user == default(Regex) || ...;
 | 
			
		||||
			//isMatch &= password == default(Regex) || ...;
 | 
			
		||||
			isMatch &= host.IsMatch(url.Host);
 | 
			
		||||
			isMatch &= !port.HasValue || port == url.Port;
 | 
			
		||||
			//isMatch &= path == default(Regex) || ...;
 | 
			
		||||
			//isMatch &= query == default(Regex) || ...;
 | 
			
		||||
			//isMatch &= fragment == default(Regex) || ...;
 | 
			
		||||
 | 
			
		||||
			return isMatch;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void ParseExpression(string expression)
 | 
			
		||||
		{
 | 
			
		||||
			var match = Regex.Match(expression, URL_DELIMITER_PATTERN);
 | 
			
		||||
 | 
			
		||||
			//ParseScheme(match.Groups[1].Value);
 | 
			
		||||
			//ParseUser(match.Groups[2].Value);
 | 
			
		||||
			//ParsePassword(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);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			expression = Regex.Escape(expression);
 | 
			
		||||
			expression = ReplaceWildcard(expression);
 | 
			
		||||
 | 
			
		||||
			if (!hasToplevelDomain)
 | 
			
		||||
			{
 | 
			
		||||
				expression = $@"{expression}(\.[a-z]+)";
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (!hasSubdomain && !allowOnlyExactSubdomain)
 | 
			
		||||
			{
 | 
			
		||||
				expression = $@"(.+?\.)*{expression}";
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			host = Build(expression);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void ParsePort(string expression)
 | 
			
		||||
		{
 | 
			
		||||
			if (int.TryParse(expression, out var port))
 | 
			
		||||
			{
 | 
			
		||||
				this.port = port;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private Regex Build(string expression)
 | 
			
		||||
		{
 | 
			
		||||
			return new Regex($"^{expression}$", RegexOptions.IgnoreCase);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private string ReplaceWildcard(string expression)
 | 
			
		||||
		{
 | 
			
		||||
			return expression.Replace(@"\*", ".*?");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void ValidateExpression(string expression)
 | 
			
		||||
		{
 | 
			
		||||
			if (expression == default(string))
 | 
			
		||||
			{
 | 
			
		||||
				throw new ArgumentNullException(nameof(expression));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (!Regex.IsMatch(expression, @"[a-zA-Z0-9\*]+"))
 | 
			
		||||
			{
 | 
			
		||||
				throw new ArgumentException("Expression must consist of at least one alphanumeric character or asterisk!", nameof(expression));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			try
 | 
			
		||||
			{
 | 
			
		||||
				Regex.Match(expression, URL_DELIMITER_PATTERN);
 | 
			
		||||
			}
 | 
			
		||||
			catch (Exception e)
 | 
			
		||||
			{
 | 
			
		||||
				throw new ArgumentException("Expression is not a valid simplified filter expression!", nameof(expression), e);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +29,10 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
 | 
			
		|||
			MapApplicationLogAccess(rawData, settings);
 | 
			
		||||
			MapKioskMode(rawData, settings);
 | 
			
		||||
			MapUserAgentMode(rawData, settings);
 | 
			
		||||
 | 
			
		||||
			// TODO: Automatically create filter rule for start URL!
 | 
			
		||||
			//			-> Only if filter active
 | 
			
		||||
			//			-> Create mechanism for post-processing of settings?
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void MapAudioSettings(string key, object value, ApplicationSettings settings)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue